【 CF1186D,E,F】Vus the Cossack and Numbers/Vus the Cossack and a Field/Vus the Cossack and a Graph

太ex了,哭了哭了orz
后面两道平均一道花了我一天啊!

D:Vus the Cossack and Numbers

题意翻译

给定n个和为0实数ai
需要构造一个同样和为0整数序列bi ,使得对于任意i有bi= =⌈ai⌉或bi=⌊ai⌋.其中⌈ai⌉表示大于ai的最小整数,⌊ai⌋表示小于ai的最大整数。输出这个b序列
注意当且仅当ai是整数时,⌊ai⌋=⌈ai⌉.
n (1≤n≤10^5) ,ai( ∣ai∣<10^5).
输入输出样例
输入
4
4.58413
1.22491
-2.10517
-3.70387
输出
4
2
-2
-4
输入
5
-6.32509
3.30066
-0.93878
2.00000
1.96321
输出
-6
3
-1
2
2

题解

没有什么难度,而且这道题是SPJ不用害怕
在这里插入图片描述
因为数据保证所有ai的和为0,所有ai的小数加在一起也应该是个整数
且这个a数组最小的和,应该是所有ai向下取整后求和
我们只需要拿到这两个和的差值就意味着有多少个数是向上取整的

只不过特别考虑一下整数即可,因为它向下取整和向上取整对和没有影响

代码实现

#include <cstdio>
#include <cmath>
#define MAXN 100005
#define LL long long
int n;
LL result;
double a[MAXN];
int main() {
	scanf ( "%d", &n );
	for ( int i = 1;i <= n;i ++ ) {
		scanf ( "%lf", &a[i] );
		result += ( LL ) floor ( a[i] );
	}
	for ( int i = 1;i <= n;i ++ )
		if ( ( LL ) ( ceil ( a[i] ) ) == a[i] )
			printf ( "%d\n", ( int ) a[i] );
		else if ( result == 0 ) 
				printf ( "%d\n", ( int ) floor ( a[i] ) );
			else {
				printf ( "%d\n", ( int ) ceil ( a[i] ) );
				result ++;
			}
	return 0;
}

抓紧时间剩下两道简直了!!!
重点是接下来这一刀!

这道要是我自己做出来了,那可以吹一天!!

E:Vus the Cossack and a Field

题意翻译

给定一个n×m 的 0101 矩阵 a ,定义对矩阵的反转为将矩阵 a 中原来的 0 变为 1 , 1 变为 0 ,得到一个新的矩阵 r ,定义对矩阵的扩展操作为将两个原矩阵 a 的反转分别置于原矩阵的右侧和下方,将原矩阵的复制置于原矩阵的右下角,得到一个二维均为原来的两倍的矩阵,即,若原来的矩阵是 a ,则扩展一次后的矩阵是

a r ​
r a ​

现在将给定的矩阵扩展无数次,得到矩阵 c , q 次询问,每次询问给定 x1,y1,x2,y2,求矩阵中以坐标 (x1,y1) 为左上角, (x2,y2)为右下角的子矩阵中数的和。下标从 1,1 开始。

1≤n,m≤1000 1≤q≤10 ^5
0≤a i,j ≤1
1≤x1≤x2≤10^9, 1≤y1≤y2≤10^9
如:
1 0
1 1
变化第一次后:
1 0 0 1
1 1 0 0
0 1 1 0
0 0 1 1
​变化第二次后:
1 0 0 1 0 1 1 0
1 1 0 0 0 0 1 1
0 1 1 0 1 0 0 1
0 0 1 1 1 1 0 0
0 1 1 0 1 0 0 1
0 0 1 1 1 1 0 0
1 0 0 1 0 1 1 0
1 1 0 0 0 0 1 1
And so on…

输入输出样例
输入
2 2 5
10
11
1 1 8 8
2 4 5 6
1 2 7 8
3 3 6 8
5 6 7 8
输出
32
5
25
14
4
输入
2 3 7
100
101
4 12 5 17
5 4 9 4
1 4 13 18
12 1 14 9
3 10 7 18
3 15 12 17
8 6 8 12
输出
6
3
98
13
22
15
3

题解

首先我们可以定义sum[i][j]表示从1,1到i,j这个矩阵中所有1的个数
(在一个单位矩阵中,即i≤n,j ≤m)
查询的区间不一定是从1,1开始的,所以我们联想到容斥原理
query ( x2, y2 ) - query ( x1 - 1, y2 ) - query ( x2, y1 - 1 ) + query ( x1 - 1, y1 - 1 )
然后把这个矩阵单独抠出来转化为求(1,1,x,y)的矩阵和

我们把每个 n×m 的小矩阵(包括原矩阵和反矩阵)看做一个整体。为了方便表述,矩阵从上到下、从左到右标号为 0 到 ∞(注意不是从 1 开始标号),那么元素 (x,y) 所在的小矩阵为:((x−1)/n,(y−1)/m)

思考一下原矩阵a和变化后的矩阵b,原理上应该是互补的,即它们1的个数应该是n*m
因为b矩阵把a矩阵中的0变成了1,1变成了0嘛!,如图:

我们发现对于任意一行(一列),从左往右(从上往下)每两个分一组,每组内一定是 0 和 1 各一个,即两两匹配。于是我们得到:对于任何一个包含完整小矩阵的前缀矩阵,原矩阵和反矩阵的数量不完全相同。

为什么不完全呢?因为对于奇数个小矩阵的前缀大矩阵,右下角是不确定的,
如图下的第九个绿色矩阵就是不确定的,没有一个完整的矩阵与之配对
排除这种情况后,剩下的一定两两配对!

对于我们一个要求的矩阵,如图:
设 (x,y) 所在小矩阵为 (r=(x−1)/n,c=(y−1)/m),我们将前缀矩阵 (1,1,x,y) 分为若干部分分别求和
在这里插入图片描述
1.左上方的绿色部分:答案为 n⋅m⌊r⋅c/2⌋;
如果 r,c 均为奇数,那么对右下角小矩阵分类讨论,根据其正反情况计算答案。
2.左下方、右上方黄色部分:和绿色部分统计方式相同,都是利用两两配对的性质,
对于奇数情况同样分类讨论。
3.右下方红色部分:直接讨论,根据其小矩阵正反情况计算答案。
最难的问题就是怎么讨论它的正负呢?

有一个神仙结论:
设一个小矩阵的坐标为 (x,y),在此特别强调从 0 开始计数(例如上图绿色部分的右下角矩阵坐标为 (2,2)),如果 x和y的二进制的1的个数为奇数,那么该小矩阵为反矩阵
具体为什么,我也十分迷茫!!我也证明不出来
在这里插入图片描述

代码实现

由于本宝宝和我的仙女童靴一起研究笔算推导了很久
所以害怕大家看着下面的代码一脸懵逼,
我会尽量把每一个自己曾经迷茫过的细节给大家一一解释!

count就是计算这个特殊矩阵的正反
opt就是算x和y的二进制的1的个数
row算的就是不是一个完整的矩阵,左下方的黄色,如果&1=1就意味着有一个是单的
col算的就是不是一个完整的矩阵,右上方的黄色,如果&1=1就意味着有一个是单的
own算的就是x,y所在的矩阵,红色部分
I就是套的公式,只有r&1&&c&1的时候才会有一个单的完整矩阵!
r,c就是x,y在从0~∞的大矩阵的背景下的坐标

row,col,own中的传值为什么有x-xx或者y-yy
就是减掉完整的矩阵,因为它已经被I算过了

#include <cstdio>
#define MAXN 1005
#define LL long long
int n, m, q;
int sum[MAXN][MAXN];

int opt ( int x, int y ) {
	int totx = 0, toty = 0;
   	while ( x ) {
   		x &= ( x - 1 );
   		totx ++;
	}
	while ( y ) {
		y &= ( y - 1 );
		toty ++;
	}
	return ( ( totx + toty ) & 1 );
}

LL count ( int x, int y, int tmp ) {
	return tmp == 0 ? sum[x][y] : x * y - sum[x][y];
}

LL row ( int x, int y, int r, int c ) {
	int xx = r * n;
	LL ans = 1LL * c / 2 * m * ( x - xx );
	if ( c & 1 ) ans += count ( x - xx, m, opt ( r, c - 1 ) );
	return ans;
}

LL col ( int x, int y, int r, int c ) {
	int yy = c * m;
	LL ans = 1LL * r / 2 * n * ( y - yy );
	if ( r & 1 ) ans += count ( n, y - yy, opt ( r - 1, c ) );
	return ans;
}

LL I ( int x, int y, int r, int c ) {
	LL ans = 1LL * r * c / 2 * n * m;
	if ( ( r & 1 ) && ( c & 1 ) ) ans += count ( n, m, opt ( r - 1, c - 1 ) );
	return ans;
}

LL own ( int x, int y, int r, int c ) {
	int xx = r * n, yy = c * m;
	return count ( x - xx, y - yy, opt ( r, c ) );
}

LL query ( int x, int y ) {
	if ( x == 0 || y == 0 ) return 0;
	int r = ( x - 1 ) / n, c = ( y - 1 ) / m;
	if ( r == 0 && c == 0 ) return own ( x, y, r, c );
	if ( r == 0 ) return row ( x, y, r ,c ) + own ( x, y, r, c );
	if ( c == 0 ) return col ( x, y, r, c ) + own ( x, y, r, c );
	return row ( x, y, r, c ) + col ( x, y, r, c ) + I ( x, y, r, c ) + own ( x, y, r, c );
}

LL research ( int x1, int y1, int x2, int y2 ) {
	return query ( x2, y2 ) - query ( x1 - 1, y2 ) - query ( x2, y1 - 1 ) + query ( x1 - 1, y1 - 1 );
}

int main() {
	scanf ( "%d %d %d", &n, &m, &q );
	for ( int i = 1;i <= n;i ++ ) {
		for ( int j = 1;j <= m;j ++ ) {
			int x;
			scanf ( "%1d", &x );
			sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + x;
		}
	}
	for ( int i = 1;i <= q;i ++ ) {
		int x1, x2, y1, y2;
		scanf ( "%d %d %d %d", &x1, &y1, &x2, &y2 );
		printf ( "%lld\n", research ( x1, y1, x2, y2 ) );
	}
	return 0;
} 

F:Vus the Cossack and a Graph

题目

给定nn个节点,mm条边的无向图,记di为第i个点的度。
一个点的度是这个点上连的边数。
Vus需要保留⌈(n+m)/2⌉条边,并保证对于任意一个点i满足f
i ≥⌈di / 2⌉其中fi表示i点在保留的图中的度。
求Vus需要保留哪些边。

暴力题解

首先这个题是可以暴力的,然后随机排序一波后就很难被hack!
在这里插入图片描述

代码实现

#include <algorithm>
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
#define MAXN 1000005
struct node {
	int u, v, num;
	node () {}
	node ( int U, int V, int Num ) {
		u = U;
		v = V;
		num = Num;
	}
};
vector < node > G;
int n, m;
int d[MAXN];
int f[MAXN];
bool flag[MAXN];
int main() {
	scanf ( "%d %d", &n, &m );
	for ( int i = 1;i <= m;i ++ ) {
		int u, v;
		scanf ( "%d %d", &u, &v );
		G.push_back ( node ( u, v, i ) );
		d[u] ++;d[v] ++;
	}
	for ( int i = 1;i <= n;i ++ )
		f[i] = ( d[i] + 1 ) >> 1;
	random_shuffle ( G.begin(), G.end() );
	int k = m;int p = ( n + m + 1 ) >> 1;
	for ( int i = 0;i < m && k > p;i ++ ) {
		int u = G[i].u, v = G[i].v;
		if ( d[u] == f[u] ) continue;
		if ( d[v] == f[v] ) continue;
		d[u] --;
		d[v] --;
		k --;
		flag[i] = 1;
	}
	printf ( "%d\n", k );
	for ( int i = 0;i < m;i ++ )
		if ( ! flag[i] )
			printf ( "%d %d\n", G[i].u, G[i].v );
	return 0;
}

如果就这么水了,这道题就没有存在的价值了!
在这里插入图片描述
秉着发扬中华民族传统美德!
富强民主文明和谐,自由平等公正法治,爱国敬业诚信友善!
还是要学会正解哒!亲╭(╯3╰)╮

官方题解

首先就要全开马力消灭下面这个错误的想法:
我们for循环到i,然后就out点i的部分边,这样的你只会保证i满足题意
但是万一i删的边与前面有藕断丝连 地下情,你斩断后说不定前面就满足不了题意

我们要保证i点删的边对前面不会造成答案影响,因而想到了欧拉回路!!
这个点有多少边,就会进多少次栈,每次删一条有关u的边,都会顺便判断一下u

欧拉回路的模板这里就不送上了,
欧拉回路就是每边必须且只遍历一次,起点即是终点
这里是无向边,那么就要保证每个点的度(有多少条边与它相连)是偶数,
这样才能一条边出去一条边回来

所以我们对于输入时是奇数边的点,可以与0建立一条虚无向边
这样每个点包括0都是偶数条边,
不可能存在输入后偶数个是奇数边的点woo!
这里我就不去证,如果有想不懂得,可以留言,我会告诉你为什么

题目也是处处留情登徒浪子 没有讲明是不是完全图,有可能存在多个互不相关的图
就要跑一遍for循环

接下来为了满足留下的边的边数,我们可以保留奇数边,第1,3,5…条边,删掉偶数边
这样就保证了留下边的边数≤(n+m)/2(向上取整)而且也保证了每一个i的f(i)
因为我们跑得是欧拉回路,一个点有多少条边,就会进多少次(边数)/2的栈,
因为一条出去一条回来嘛!这也是为什么选择欧拉回路来做这道题

接下来就是对于实边虚边的保留:
首先虚边使我们擅作主张加的,我们必须消灭作案证据,不能让它出现在大众面前!
所以就算我们保留奇数边,它也必须是个实的!

那么对于偶数边,我们也是尽量删掉虚边,才能尽量满足f,
只要这条边不是虚边,并且它左右两边有一条是虚边,都可以保留这条边
所以只有当这条边和它的左右两边都是实边的时候才迫不得已删掉

代码实现

如果是用vector跑的欧拉回路的话,
一定要注意for循环不药每次都把这个点的每一条边询问一次,会T!!!我被卡了1天
vector又不会跳过边,那么询问到u,通过i这条边递归v,
再次询问到u的时候,一定不会走1~i这些边了
一定都已经处理过了!!
所以帅气又多金的我就用了last来记录一下上一次走到了哪条边(优秀!)

那个opt赋值成2的,小可爱们可以画一个欧拉回路,简单的就可以了
最后你会发现,起点就在栈底,终点就在栈顶,
那么连接栈顶和栈顶-1的这条边的下面一条边就是连接栈底和栈底+1的边

#include <cstdio>
#include <vector>
using namespace std;
#define MAXN 1000005
struct node {
	int v, edge;
	node () {}
	node ( int V, int E ) {
		v = V;
		edge = E;
	}
};
vector < pair < int, int > > result;
vector < node > G[MAXN];
int n, m, cnt, Top;
int d[MAXN];
int last[MAXN]; 
int tag_time[MAXN];
int sta[MAXN << 2];
bool vis[MAXN << 2];
void dfs ( int u ) {
	tag_time[u] ++;
	int ip = tag_time[u];
	for ( int i = last[u];i < G[u].size();i ++ ) {
		int v = G[u][i].v, num = G[u][i].edge;
		last[u] = i + 1;
		if ( vis[num] ) 
			continue;
		vis[num] = 1;
		dfs ( v );
		if ( tag_time[u] > ip ) break;
	}
	sta[++ Top] = u;
}
int main() {
	scanf ( "%d %d", &n, &m );
	for ( int i = 1;i <= m;i ++ ) {
		int u, v;
		scanf ( "%d %d", &u, &v );
		cnt ++;
		G[u].push_back ( node ( v, cnt ) );
		G[v].push_back ( node ( u, cnt ) );
		d[u] ++;d[v] ++;
	}
	for ( int i = 1;i <= n;i ++ )
		if ( d[i] & 1 ) {
			cnt ++;
			G[i].push_back ( node ( 0, cnt ) );
			G[0].push_back ( node ( i, cnt ) );
		}
	for ( int i = 1;i <= n;i ++ ) {
		if ( ! tag_time[i] ) {
			Top = 0;
			dfs ( i );
			for ( int j = 1;j + 1 <= Top;j += 2 )
				if ( sta[j] && sta[j + 1] )
					result.push_back ( make_pair ( sta[j], sta[j + 1] ) );
			for ( int j = 2;j + 1 <= Top;j += 2 ) {
				int opt = j + 2;
				if ( j + 1 == Top ) opt = 2;
				if ( ! ( sta[j] && sta[j - 1] && sta[j + 1] && sta[opt] ) && sta[j] && sta[j + 1] )
					result.push_back ( make_pair ( sta[j], sta[j + 1] ) );
			}
		}
	}
	printf ( "%d\n", result.size() );
	for ( int i = 0;i < result.size();i ++ )
		printf ( "%d %d\n", result[i].first, result[i].second );
	return 0;
} 

好了,还有什么不懂得都可以留言,我会补充出来!
终于把这个恶心的草丛三婊 题给搞定了,下期再见!
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值