Codeforces Round 395 div1
Codeforces Round 395 div1
tags
同余方程
、费马小定理
、树hash
、换根
、线段树
、并查集
码量细节:E>C>D>A>B
难度:C>E>D>B>A
A. Timofey and a tree
题意:给一棵点染色树,求一个根节点,使得除了原树以外所有的子树节点颜色都一样。
题解:首先考虑直接维护颜色相同的联通块,处理出所有颜色一样的块后如果块形成的树直径不超过3就有机会找到答案,但是由于3个块中间的宽度可能不是1,导致找不到一个根节点好像很不好处理。考虑直接维护颜色分界的边。如果有解,那么分界边一定会共享同一个节点,问题变成n个pair,求是否存在一个x在这n个pair中都出现过,就可以线性求解了。如果不存在分界边则任意选一个节点都满足条件。
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/11
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+50;
int main(int argc, char* argv[]) {
int n;
scanf("%d", &n);
vector<pair<int, int> > a(n - 1);
for (int i = 0; i < n - 1; ++i) scanf("%d%d", &a[i].first, &a[i].second);
vector<int> c(n + 1);
for (int i = 1; i <= n; ++i) scanf("%d", &c[i]);
vector<pair<int, int> > x;
for (auto i : a) {
if (c[i.first] != c[i.second]) {
x.emplace_back(i);
}
}
if (x.empty()) printf("YES\n1\n");
else {
int flag = 1;
for (auto i : x) {
if (i.first == x[0].first || i.second == x[0].first) {
continue;
}
flag = 0;
break;
}
if (flag) printf("YES\n%d\n", x[0].first);
else {
flag = 1;
for (auto i : x) {
if (i.first == x[0].second || i.second == x[0].second) continue;
flag = 0;
break;
}
if (flag) printf("YES\n%d\n", x[0].second);
else printf("NO\n");
}
}
return 0;
}
B. Timofey and rectangles
题意:给一些矩形的位置,求是否能用四种颜色染色使得没有紧靠的矩形同色,求方案。额外条件:没有矩形相交或相包含,所有矩形的边长都为奇数。
题解:边长为奇数可以得出四种情况,分别是行和列上的:1、下奇上偶,2、下偶上奇,3、左奇右偶,4、左偶右奇,其中(1,2),(3,4)
可以两两相邻,所以直接对某个边界点encoding即可,一定存在合法解。
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/11
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e5+50;
int main(int argc, char* argv[]) {
int n;
scanf("%d", &n);
printf("YES\n");
for (int i = 0; i < n; ++i) {
int xl, yl, xr, yr;
scanf("%d%d%d%d", &xl, &yl, &xr, &yr);
int t1 = (yl % 2 + 2) % 2;
int t2 = (xl % 2 + 2) % 2;
printf("%d\n", t2 * 2 + t1 + 1);
}
return 0;
}
C. Timofey and remoduling
题意:在模m(素数)意义下,求长度为n的序列\(a_i=a_{i-1}+d\),等于题目给出的n个数字的一种排列。
题解:首先\(a_i=a_{i-1}+d\)在模m是素数意义下的循环节为m,也就是说在1~n
中,\(a_i\)都是distinct的,然后考虑一个问题\(a_i+d*k=a_j\)这个式子什么时候会有j
的唯一解,显然是一个循环节内的情况。考虑当n*2<m
时,对于\(a_i+n*d\),有\(i+n<m\),那么也就是说对于任意的\(a_j-a_i=(j-i)d\),\(j-i\)是不超过\(n\)的,也就是说\(a_j+d*k=a_j\)有唯一解\(j\),当然反之若n*2>=m
时,就可能出现溢出一个循环节的情况。那么对于n*2<m
我们可以枚举任意一组\((i,j)\),对于他们找出所有可能的\(a_x+(j-i)*d=a_y\)个数,那么可以说一共有\(n-(j-i)+1\)组,枚举解出组数之后也就知道了\((i,j)\)之间的距离,同时也知道\(a_j\)和\(a_i\),所以就可以求出\(d\),那么对于任意的\(a_i\),求出\(a_0\)也就只需要不断的\(-d\)即可,解出\(a_0\)和\(d\)就可以\(O(nlogn)\)的check是否满足题意。对于n*2>=m
则需要求出补集,也就是后半段循环,这个循环与题目要求的循环的差别只在于\(a_0\)的不同,也就是存在一个\(n*d\)的偏移量,就可以很方便的解出答案。
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/11
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int n, m;
int x, d;
// 1 2 3 4 5
// k = 3
// 1 4, 2 5 tmp = 2
void solve(const vector<int>& a) {
if (a.size() == 1) {
x = a[0], d = 1;
return;
}
set<int> mp;
for (auto i : a) mp.insert(i);
int t = a[1] - a[0];
int tmp = 0;
for (auto i : a) {
if (mp.count((i + t) % m)) tmp ++;
}
int k = a.size() - tmp;
x = a[0];
d = 1ll * t * qp(k, m - 2, m) % m;
while (mp.count((x - d + m) % m)) x = (x - d + m) % m;
for (int i = 0; i < a.size(); ++i) {
if (!mp.count((x + 1ll * i * d) % m)) {
x = -1;
return;
}
}
}
int main(int argc, char* argv[]) {
scanf("%d%d", &m, &n);
vector<int> a(n);
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
sort(a.begin(), a.end());
if (n == 1) {
printf("%d %d\n", a[0], 0);
return 0;
} else if (n == m) {
for (int i = 0; i < n; ++i) {
if (a[i] != i) {
printf("-1\n");
return 0;
}
}
printf("0 1");
return 0;
}
if (n * 2 < m) {
solve(a);
} else {
vector<int> inva;
int pos = 0;
for (int i = 0; i < m; ++i) {
if (pos < a.size() && a[pos] == i) pos ++;
else inva.push_back(i);
}
solve(inva);
if (x != -1) x = ((x - 1ll * n * d % m) % m + m) % m;
}
if (x == -1) printf("-1\n");
else printf("%d %d\n", x, d);
return 0;
}
D. Timofey and a flat tree
题意:对于一棵树,求一个根,使得本质不同子树个数最大。
题解:摆明了是树hash换根,如果在hash策略上做一些调整就可以很方便的做换跟。考虑对子树进行加法hash,也就是说\(hs[u]=1+\sum pow(p,hs[v])\),这样做的好处是对于减掉一个子树,只需要在模意义下做减法。考虑在\(u\to v\)这条边上做换根,显然对于\(hs[u]-=hs[v]\),\(hs[v]+=hs[u]\)就ok了,回溯的时候再逆向加回去。
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/10
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+50;
const int mod = 998244353;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int n;
int hs = 79;
vector<int> edge[maxn];
int dp[maxn];
map<int, int> mp;
int now;
int res, respos;
void add(int x) { if (mp[x] ++ == 0) now ++; }
void del(int x) { if (--mp[x] == 0) now --; }
void dfs(int u, int pre) {
for (auto v : edge[u]) {
if (v == pre) continue;
dfs(v, u);
dp[u] = (dp[u] + qp(hs, dp[v])) % mod;
}
dp[u] ++;
add(dp[u]);
}
void dfs2(int u, int pre) {
if (now > res) {
res = now;
respos = u;
}
for (auto v : edge[u]) {
if (v == pre) continue;
del(dp[u]);
del(dp[v]);
dp[u] = (dp[u] - qp(hs, dp[v])) % mod;
if (dp[u] < 0) dp[u] += mod;
dp[v] = (dp[v] + qp(hs, dp[u])) % mod;
add(dp[u]);
add(dp[v]);
dfs2(v, u);
del(dp[u]);
del(dp[v]);
dp[v] = (dp[v] - qp(hs, dp[u])) % mod;
if (dp[v] < 0) dp[v] += mod;
dp[u] = (dp[u] + qp(hs, dp[v])) % mod;
add(dp[u]);
add(dp[v]);
}
}
int main(int argc, char* argv[]) {
scanf("%d", &n);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1, 0);
res = now;
respos = 1;
dfs2(1, 0);
printf("%d\n", respos);
return 0;
}
E. Timofey and our friends animals
题意:给一张图,每条边的节点编号差不超过5,m次询问\(l \to r\)这些节点构成多少个联通块。
题解:首先最直观的想法当然是线段树维护左右两端5个数字的联通性,光是想想代码量就上天。当然我也只会这样写,复杂度\(O(nlognk^2)\)。另外有一个做法是:对于\(l \to r\),联通块个数是其中包含的全局联通块个数+不超过10个块,这不超过10个块是\([l-5,l)\)和\((r,r+5]\)决定的,具体来说还会有一些细节需要考虑,复杂度会降到\(O(nk^2)\)。还有LCT的做法,可以叉掉出题人,因为不需要编号不超过5这个条件,可以做任意图的联通块个数,总之这是个假题。
代码:
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/5/8
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 50;
const int mod = 1e9 + 7;
int n, m, K;
int edge[maxn][5];
struct node {
int lpos, rpos;
int val, len;
int lval[5], rval[5];
node() {
lpos = rpos = 0;
val = len = 0;
// for (int i = 0; i < 5; ++i) pos[i].clear();
}
} p[maxn << 2];
int le, re;
int pre[maxn];
int f(int x) { return x == pre[x] ? x : pre[x] = f(pre[x]); }
inline node comb(node a, node b) {
if (a.len == 0) return b;
node res;
res.lpos = a.lpos, res.rpos = b.rpos;
res.len = a.len + b.len;
res.val = a.val + b.val;
int llt = min(K, a.len);
int rrt = min(K, b.len);
int rt = min(K, res.len);
for (int i = 0; i < llt; ++i) pre[a.rval[i]] = a.rval[i], pre[a.lval[i]] = a.lval[i];
for (int i = 0; i < rrt; ++i) pre[b.lval[i]] = b.lval[i], pre[b.rval[i]] = b.rval[i];
for (int i = a.rpos - llt + 1; i <= a.rpos; ++i) {
for (int j = max(0, b.lpos - i - 1); j < K && j + i + 1 < b.lpos + rrt; ++j) {
if (edge[i][j] == 0) continue;
int now = i + j + 1;
if (now > b.rpos || now < b.lpos) continue;
int u = f(b.lval[now - b.lpos]), v = f(a.rval[a.rpos - i]);
if (u != v) {
res.val--;
pre[u] = v;
}
}
}
for (int i = 0; i < llt; ++i) res.lval[i] = f(a.lval[i]);
for (int i = 0; i < rt - llt; ++i) res.lval[i + llt] = f(b.lval[i]);
for (int i = 0; i < rrt; ++i) res.rval[i] = f(b.rval[i]);
for (int i = 0; i < rt - rrt; ++i) res.rval[i + rrt] = f(a.rval[i]);
return res;
}
inline void build(int rt, int l, int r) {
if (l == r) {
p[rt].len = p[rt].val = 1;
p[rt].lpos = p[rt].rpos = l;
p[rt].lval[0] = l;
p[rt].rval[0] = l;
return;
}
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
p[rt] = comb(p[rt << 1], p[rt << 1 | 1]);
}
inline node query(int rt, int l, int r) {
if (le <= l && r <= re) return p[rt];
int mid = l + r >> 1;
node res;
if (le <= mid) res = comb(res, query(rt << 1, l, mid));
if (re > mid) res = comb(res, query(rt << 1 | 1, mid + 1, r));
return res;
}
int main(int argc, char *argv[]) {
// freopen("data.in","r",stdin);
// freopen("data.out", "w", stdout);
// clock_t ST = clock();
scanf("%d%d", &n, &K);
scanf("%d", &m);
for (int i = 0; i < m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
if (u > v) swap(u, v);
edge[u][v - u - 1] = 1;
}
build(1, 1, n);
// cerr << "time: " << ((clock() - ST) * 1000.0 / CLOCKS_PER_SEC) << "ms" << endl;
int q;
scanf("%d", &q);
for (int i = 1; i <= q; ++i) {
scanf("%d%d", &le, &re);
printf("%d\n", query(1, 1, n).val);
}
// cerr << "time: " << ((clock() - ST) * 1000.0 / CLOCKS_PER_SEC) << "ms" << endl;
return 0;
}