题目链接:https://codeforces.com/contest/1189/problem/D2
A - Keanu Reeves
题意:定义一个01字符串是“good”的,当且仅当它是不“balance”的。把一个字符串分成尽可能少的不“good”的串。
题解:假如它自己本身不“good”,那么就是最少的1。否则最少要有2个串,下面是一个构造:因为整个字符串是“balance”的,所以去掉第一个字符之后剩下的就“good”了,而一个字符当然也是“good”的。
char s[105];
void test_case() {
int n;
scanf("%d%s", &n, s + 1);
int cnt = 0;
for(int i = 1; i <= n; ++i) {
if(s[i] == '0')
++cnt;
else
--cnt;
}
if(cnt != 0)
printf("1
%s
", s + 1);
else
printf("2
%c %s
", s[1], s + 2);
}
B - Number Circle
题意:给一串数字,重新排列它,使得任意一个位置都严格小于它的两个邻居的和。
题解:大概有个直觉是,这种题先考虑排序?排序完之后显然 (i in [1,n-1]) 都满足,但是 (a[n]<a[1]+a[n-1]) 就一定是No吗?假如有 (a[n]<a[n-1]+a[n-2]) 那么至少这个元素是满足的,这时候被干扰的 (n-1,n-2,1) 这三个位置都显然依然满足,所以只需要排序之后交换一下最后两个元素的位置并check就可以了。
int a[100005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
if(a[n - 1] + a[n - 2] <= a[n]) {
puts("NO");
return;
}
swap(a[n - 1], a[n]);
puts("YES");
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i], "
"[i == n]);
}
C - Candies!
题意:每次选一段长度为 (2^k) 的区间,每次把同层的相邻两个元素 (a,b) 在下一层用 (c=(a+b)mod 10) 替换,假如 $a+bgeq 10 $ 则产生1的贡献,直到该层只剩下1个元素。每次询问 ([l,r]) ,保证长度一定是 (2^k) 求总贡献。
题解:仔细观察规律或者大胆猜想之后会发现,最后的结构是全部元素求和再除以10。因为每次这个取模操作并产生1的贡献就很像进位,最后就问进位了多少次。
int a[100005];
int prefix[100005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
for(int i = 1; i <= n; ++i)
prefix[i] = prefix[i - 1] + a[i];
int q;
scanf("%d", &q);
while(q--) {
int l, r;
scanf("%d%d", &l, &r);
printf("%d
", (prefix[r] - prefix[l - 1]) / 10);
}
}
D1 - Add on a Tree
赛后看了一下榜,貌似D1这次的解法会显然简单于D2。大哭,仔细看了这俩题不应该按以前那样命名的(不过确实假如不是EasyVersion)……或者说要挑战这种题之前应该先看看榜是不是有实力比我稍高的人只过了D1。
题意:给一棵 (n(2leq n leq 10^5)) 个节点的树,初始边权都为0。每次选一对叶子,把叶子的路径上的值全部加一个可正可负的实数。问是否可以通过有限次修改使得树的各个边权的组合可以取遍任意实数的组合?
题解:首先,可以观察出一个没啥用的规律,规律1:假如给一条链,那么除非只有两个节点是Yes,其他的必定是No。然后这个东西给了一个启发,规律2:假如有个度数为2的节点,那么它必然是某条长度>=3的“链”的中间,答案肯定也是No。显然规律2包含规律1。那么不存在度数为2的节点是不是一定是Yes呢?假如是比赛的话肯定可以莽,因为这个树肯定是只和构型有关。
那么考虑一个三岔路口如下。
OX,OY,OZ
想给OX置为某个实数x,那么可以有:
(X,Y,x/2);
(X,Z,x/2);
(Y,Z,-x/2);
这样把OX的整条路都置为了x,也就是说对于一个任意一个以某个O为根的树,且O的度数>=3。那么可以给到根O到任意叶子的路径同时加上任意实数x。那么可以通过这个方法把根连向子树根的边一次修改完成,然后可以递归下去,注意子树实际上可以使用到父亲的路,所以子树只要求去掉父亲之后的度>=2。
也就是,证明了假如除了叶子以外的所有节点,若度>=3则Yes。假如出现度2则No。叶子的度1,所以意思是只需要判断是不是存在度恰好为2的节点。
int deg[100005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n - 1; ++i) {
int x, y;
scanf("%d%d", &x, &y);
++deg[x];
++deg[y];
}
for(int i = 1; i <= n; ++i) {
if(deg[i] == 2) {
puts("NO");
return;
}
}
puts("YES");
return;
}
*D2 - Add on a Tree: Revolution
题意:给一棵 (n(2leq n leq 10^5)) 个节点的边带权树,初始边权都为0。问能否把其边权修改为题目给的样子。题目给的边权都是两两不同的非负偶数权。每次选一对叶子,把叶子的路径上的值全部加一个可正可负的整数。可行要求输出构造方案。
题解:想到前面的No的条件是因为有边会相等,那么现在边不等之后是不是一定能构造呢?而且x为偶数使得x/2确实也是整数。进行三次操作之后相当于给根到叶子的路径全部+x。考虑一个像树剖一样的数据结构?事实上并没有询问操作,所以可以直接打差分标记?
由于n>=2,所以叶子度是恰好为1的。先特判掉n==2的情况,直接构造输出。然后必定存在一个不是叶子的节点,以这个第一个这种节点作为根r。
首先要尽可能快的找出通过上述的三岔路口的X的一个叶子。从根r开始dfs,叶子们用自己作为代表自己的代表叶子,非叶子的节点用第一个子树的代表叶子作为自己的代表叶子,那么用的时候就可以快速确定要哪个叶子。对于有父亲的节点,规定父亲作为Y,当处理第一个子树时用第二个子树作为Z;当处理其他子树时规定用第一个子树作为Z。对于根r而言,处理前两个子树的时候互相作为XY,第三个子树作为Z;从第三个子树开始用第一个子树和第二个子树作为Y和Z。从根递归下去的时候,带上一个不在那个子树的叶子。
单次修改则会输出三行,其中对Y和Z的修改已经抵消了,无所谓,然后要给当前的根到X的代表叶子的路径全部加x,这时候给X子树打上标记+x即可,在对X子树的第一个子树进行构造时要去掉标记已有的值,对其他子树构造时不需要进行改变。
注意要下推lazy标记!
int n;
vector<pii> G[1005];
vector<pii> son[1005];
int fl[1005];
int lz[1005];
struct Output {
int u, v, x;
Output(int uu, int vv, int xx) {
u = uu;
v = vv;
x = xx;
}
};
vector<Output> output;
void dfs(int u, int p) {
//printf("u=%d
", u);
if(G[u].size() == 1) {
fl[u] = u;
return;
}
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i].first;
if(v == p)
continue;
son[u].push_back(G[u][i]);
}
for(int i = 0; i < son[u].size(); ++i) {
int v = son[u][i].first;
dfs(v, u);
if(i == 0)
fl[u] = fl[v];
}
return;
}
void AddOutput(int u, int v, int w, int val) {
assert(val % 2 == 0);
output.push_back(Output(u, v, val / 2));
output.push_back(Output(u, w, val / 2));
output.push_back(Output(v, w, -val / 2));
}
void dfs2(int u, int p, int pl) {
if(G[u].size() == 1)
return;
if(p == -1) {
assert(G[u].size() >= 3);
assert(son[u].size() == G[u].size());
assert(lz[u] == 0);
int X = G[u][0].first;
int Y = G[u][1].first;
int Z = G[u][2].first;
int V = G[u][0].second;
assert(lz[X] == 0);
AddOutput(fl[X], fl[Y], fl[Z], V);
lz[X] += V;
X = G[u][1].first;
Y = G[u][0].first;
Z = G[u][2].first;
V = G[u][1].second;
assert(lz[X] == 0);
AddOutput(fl[X], fl[Y], fl[Z], V);
lz[X] += V;
Y = G[u][0].first;
Z = G[u][1].first;
for(int i = 2; i < G[u].size(); ++i) {
X = G[u][i].first;
V = G[u][i].second;
assert(lz[X] == 0);
AddOutput(fl[X], fl[Y], fl[Z], V);
lz[X] += V;
}
int pl1 = fl[G[u][0].first], pl2 = fl[G[u][1].first];
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i].first;
dfs2(v, u, (i == 0) ? pl2 : pl1);
}
return;
}
//u is not a root
assert(G[u].size() >= 3);
assert(son[u].size() == G[u].size() - 1);
int X = son[u][0].first;
int Z = son[u][1].first;
int V = son[u][0].second - lz[u];
assert(lz[X] == 0);
AddOutput(fl[X], pl, fl[Z], V);
lz[X] += V + lz[u];
for(int i = 1; i < son[u].size(); ++i) {
int X = son[u][i].first;
int Z = son[u][0].first;
int V = son[u][i].second;
assert(lz[X] == 0);
AddOutput(fl[X], pl, fl[Z], V);
lz[X] += V;
}
for(int i = 0; i < son[u].size(); ++i) {
int v = son[u][i].first;
dfs2(v, u, pl);
}
return;
}
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n - 1; ++i) {
int x, y, w;
scanf("%d%d%d", &x, &y, &w);
G[x].push_back({y, w});
G[y].push_back({x, w});
}
if(n == 2) {
puts("YES");
puts("1");
printf("1 2 %d
", G[1][0].second);
return;
}
int r = -1;
for(int i = 1; i <= n; ++i) {
if(G[i].size() == 2) {
puts("NO");
return;
}
if(r == -1 && G[i].size() >= 3)
r = i;
}
puts("YES");
dfs(r, -1);
dfs2(r, -1, -1);
//printf("r=%d
", r);
assert(output.size() % 3 == 0);
printf("%d
", (int)output.size());
for(int i = 0; i < output.size(); ++i)
printf("%d %d %d
", output[i].u, output[i].v, output[i].x);
return;
}
总结:第一次写树上差分的题?找到规律之后就转化成对某个根到叶子全部加一个值,每次都规定用这个子树的第一个叶子(这个,不如叫做“首链剖分”?),所以lazy的值只由第一个叶子进行继承。和树上差分解决对路径整体加值不同,这个有一端是叶子所以不需要打结束标记。这么看来能用树上差分的题肯定是可以离线的,所以假如用Tarjan离线处理lca之后可以做到整体 (O(n)) ?
对于这道题来说这个复杂度完全可以 (O(n^2)) 那么就去掉那个lazy下推的过程,每次暴力把值全部加到路上就可以了。
*E - Count Pairs
???为什么这道题放这个位置?还这么多分?
题意:给定 (n(1leq n leq 3 cdot 10^5),p(2leq p leq 10^9)p is a prime,k(0leq k leq p-1)) ,和一组互不相同的数 (a_i(0leq ai leq p-1)) 求有多少个数对 $(i,j)(i<j) $ 满足 $(a_i+a_j)(a_i^2+a_j^2) equiv k mod p $ 。
题解:看这个形式怎么有平方和?假如有平方差我肯定想展开,但是这里又给一个和。假如展开的话两个数就纠缠在一起很麻烦,尝试配个 ((a_i-a_j)) 。
???不是用个map就随便出来了?
看了题解才发现这里的 (a_i) 是不等的,可是等于的话会有什么问题呢?这里并没有除法啊。其实是假如有元素相同的话就会有增根,也就是原式 ((a_i+a_j)(a_i^2+a_j^2) equiv k mod p) 变成 (4x^3 equiv k mod p) ,当 (x) 取任意值时该式不见得成立。修复的办法很简单,先把重复元素之间的影响的去掉,化归为本问题,再单独验证每种重复元素是否满足等式 (4x^3 equiv k mod p) ,其实这个是不是可以解n次剩余呢?
int n, p, k;
map<int, int> M;
int calc(ll x) {
ll res = (-k * x) % p;
ll x2 = x * x % p;
res += x2 * x2;
res %= p;
if(res < 0)
res += p;
return (int)res;
}
void test_case() {
scanf("%d%d%d", &n, &p, &k);
ll sum = 0;
for(int i = 1, x; i <= n; ++i) {
scanf("%d", &x);
int cx = calc(x);
sum += M[cx];
++M[cx];
}
printf("%lld
", sum);
}
总结:等式两边同时乘以某个不为0的因式才不会出现增根,否则可能解出的根是自己加的因式的根而不是原式的根。
F题IGM难度,都2700了,不了不了。