太ex了,哭了哭了orz
后面两道平均一道花了我一天啊!
文章目录
- D:Vus the Cossack and Numbers
- 题意翻译
- 题解
- 代码实现
- E:Vus the Cossack and a Field
- 题意翻译
- 题解
- 代码实现
- F:Vus the Cossack and a Graph
- 题目
- 暴力题解
- 代码实现
- 官方题解
- 代码实现
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;
}
好了,还有什么不懂得都可以留言,我会补充出来!
终于把这个恶心的草丛三婊 题给搞定了,下期再见!