题目链接:https://codeforces.com/contest/1327
第一次在比赛中通过2200分难度的题目,鼓励一下自己,可以有机会不通过手速上橙色了。而且也准备突破历史上的最高纪录了,还差47分,加油。
当时开题的顺序如上图所示。
A - Sum of Odd Integers
现在的A题送分都要卡我十分钟去想。
题意:给一个比较大的整数n,和一个比较大的整数k,问是否能选出恰好k个不同的奇数,使得他们的和为n。
题解:贪心,从1,3,5,7,9,...这样选,选一共k个,用等差数列求和公式得:k个最小的奇数的和为:k*k,当时觉得很诡异,不过验算一下前几项确实是这样。
那么贪心完成kk(注意不要溢出)之后,若已经比n大,那么无解。否则记剩余的部分r=n-kk,若r是奇数,那么和前面的任意一个组合都是得到偶数,也是无解。(其实k个奇数,若k是偶数,那么和肯定是偶数,否则和一定是奇数,也可以这样判断)否则把这个r加给最大的奇数,这样会让这个奇数有可能变成一个更大的奇数,反正就和小的那些不同了。
B - Princesses and Princes
这个B题就比A题水很多了。
题意:一个二分图,有n个左部点,有n个右部点,不超过可以接受的范围的边数,没有重边。然后从[1,n]按顺序遍历每个左部点,每个左部点都选择与它连接的右部点中没有被选择过的最小编号的那个,若某个左部点无法选择就会被跳过。问是否可以通过加入恰好1条边,使得匹配数变大。
题解:首先想清楚一点:加入一条边,有可能使得匹配数变小(就是说,指使一个编号小的左部点,占了原本分配给编号大的左部点的右部点,导致那个编号大的失配),有可能不变(很显然可以不变,加一条边连接一个比被选择的右部点编号更大的点,样例有),有可能变大(样例有)。非常显然,先按题意模拟,假如模拟完成之后所有点都匹配了,那就不可能变大,否则至少剩下一个左部点和一个右部点,直接把这两个连起来,显然这样连着的点,不会影响其他点的选择。因为这个左部点要么没有连接右部点,要么连接的右部点都被编号更小的左部点选掉了,也是相当于没有连接。
C - Game with Chips
又继续贪心。
题意:有一个n*m的棋盘格,上面有若干个薯片,薯片可以重叠。每个薯片需要途径一个属于这个薯片的终点,注意不需要在结束的时候停留在终点。每次可以摇动棋盘,使得所有的薯片向同一个方向一起移动。这个棋盘四周有墙,薯片向墙移动会什么都不发生。求是否可以摇动不超过2nm次使得所有薯片都途径他自己需要途径的终点。
题解:可以用n-1次U或者D来弄到边界上,然后用m-1次L或R来弄到一个角落里,然后让这堆薯片从这个角落开始遍历整个图,需要nm-1次,简单数学证明会发现这三个的和不可能超过2nm:
\(f(n,m)=(2nm)-(n-1+m-1+nm-1)\)
问这个 \(f(n,m)\) 是否恒为非负。
\(f(n,m)=nm-(n+m)+3\)
求偏导,可以知道最小值位于 \(f(1,1)=2\)
所以这个构造成立。
*D - Infinite Path
题意:给出一个n个数字排列,定义一条无穷路,为 \(i,p[i],p[p[i]],p[p[p[i]]]...\) ,每个数字有一种颜色,求一个最小的正整数k,使得这个排列的k次方,存在一条无穷的同色路。
题解:这种置换/排列的题目,都是连边 \((i,p[i])\) ,这样一个排列就完全对应一个若干个环的有向图。这样的排列的平方,就相当于这个图里面从i点走两步,这样长度为偶数的环就会分裂成两个等大的环,长度为奇数的环会进行一次路径压缩。容易知道,一个长度为n环,走k步,当g=gcd(n,k)=1时,会进行路径压缩,否则会分裂成g个长度为n/g的环,这时就有一种想法,就是验证每个环的质因数,不过很可惜这样是错的(WA2,这个错误是很显然的,因为比如n=12,若取k=6,那么一个环只有2个数,就比k=3一个环有四个数,要容易同色)。应该是验证每个环的因数。这样复杂度就是 \(O(n\sqrt{n})\) 但是一个数的因数会有多少种呢?可能也就300多种吧,远远不及 \(O(\sqrt{n})\) ,所以中间不一定是乘法连接的。
const int MAXN = 2e5;
int G[200005];
bool vis[200005];
int c[200005];
int cx[200005], x;
bool check(int d) {
for(int i = 1; i <= d; ++i) {
int hcx = cx[i];
int suc = 1;
for(int j = i + d; j <= x; j += d) {
if(cx[j] != hcx) {
suc = 0;
break;
}
}
if(suc)
return 1;
}
return 0;
}
int ans;
int dfs(int u) {
x = 0;
cx[++x] = c[u];
vis[u] = 1;
while(!vis[G[u]]) {
u = G[u];
cx[++x] = c[u];
vis[u] = 1;
}
if(1 < ans) {
if(check(1))
return 1;
} else
return ans;
int tmp = INF;
for(int i = 1; i < ans && i * i <= x; ++i) {
if(x % i == 0) {
if(check(i))
return i;
if(i * i != x) {
if(x / i < ans) {
if(check(x / i))
tmp = min(tmp, x / i);
}
}
}
}
return tmp;
}
void TestCase() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &G[i]);
vis[i] = 0;
}
for(int i = 1; i <= n; ++i)
scanf("%d", &c[i]);
ans = INF;
for(int i = 1; i <= n; ++i) {
if(!vis[i])
ans = min(ans, dfs(i));
}
printf("%d\n", ans);
}
*E - Count The Blocks
这个和昨天(2020/3/24)做的一个远古场的edu的题一模一样。
题意:对于所有长度为n的十进制数字字符串(可以有前导零),对于所有的x in[1,n],求长度恰好为x的连续区间出现了多少次。一个连续区间是指连续相同数字的最长的区间。
题解:这个题要想清楚怎么统计不重不漏,首先枚举一个长度x,那么他的左侧可以有[0,n-x]个数,而每种不同位置中的是不会影响的,就算某种串包含多个这样的长度为x的区间,那么他会在枚举不同位置的时候被分别统计。所以只需要先选10种数字中的一种填这个区间,假设两端都至少有1个数,那么这个数可以在剩下的9种中选,其他的数任选;否则若只有一端有数,则在剩下的9种中选,其他的数任选;否则若没有剩下的数,答案显然就是10。
一个验算的思路是:把每种长度x的区间的出现次数和x相乘然后求和,这个应该是刚好等于n与10的n次方相乘。
另一种迭代的思路是:对于每种长度为n-1的字符串,假如在后面叠一个与最后一个字符相同的字符,就可以让每种长度为[1,n-1]的字符串的出现次数平移到[2,n],然后剩下的全部都是新增了长度为1的字符串(因为和最后一个字符不同,所以不会连在一起)。