CF & AT 做题记录

选一些 div 1 中的(好)题总结一下 qwq
从现在开始像 rainboy 一样打比赛 qaq

Codeforces Round #902 (CF 1876)

C Autosynthesis

给定一个 n 个数的序列 a。你可以进行若干次操作,每次操作可以在一个数 ai 上画圈。
你需要满足以下要求:设你的操作次数为 kp1k 为你每次操作时画圈的 下标,你需要满足没有画圈的数字个数 恰好为 k,且这些数字组成的子序列 b1kp1k 相等。
请你构造一组方案。如果不存在合法方案,输出 -1
1n2×105,1ain

一开始就在做,比赛唯一场切的题(
方向很清晰,就是细节有点多(?)

如果一个数 x 被圈了,那么一定存在一个没有被圈的数,满足其值为 x. 这很有启发性!把每个 ai 看作 iai ,那么这个序列就转化为一个内向基环树森林,只需对每个连通块分别求解即可。
从没有被圈的数入手,记这种数为 1 类,被圈的数为 0 类。考虑 ufa ,如果 u1 ,那么 fa 必须是 0. 继续,考虑 vu ,如果 u0 ,那么所有 v 也必须是 1. 以上都可以由题意推出。
既然是基环树,首先要找出环。从底向上推,叶子必须是 1,那么不在环上的点的状态唯一确定。对于环上点 u ,要么是 0 ,即有一个非环节点是 1 并指向它;要么不确定,即所有指向它的非环节点都是 0u 的状态取决于环上 preu 的状态,记这种 u2 类。
接下来开始分讨。注意到两个环上 0 可以相邻,对于形如 02222220 的序列,可以 01 交替染色,即 0101010100,于是只要环上有 0 就有解。如果都是 2 ,必须是 01 交替序列,相同数字必不能相邻,于是环长偶数有解,奇数无解。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// #define DeBug
// #define LOCAL
// #define TestCases
const int N = 2e5;
int n;
int a[N + 5];
vector<int> e[N + 5];
int notuse[N + 5];
int stk[N + 5], top;
int vis[N + 5];
int seq[N + N + 5], len;
int oncir[N + 5];
void findcir(int u)
{
if (vis[u])
{
len = 0;
while (stk[top] != u)
{
seq[++len] = stk[top];
top--;
}
seq[++len] = u;
reverse(seq + 1, seq + len + 1);
top--;
for (int i = 1; i <= len; i++)
{
oncir[seq[i]] = 1;
seq[i + len] = seq[i];
}
return ;
}
stk[++top] = u;
vis[u] = 1;
findcir(a[u]);
return ;
}
void dfs(int u)
{
vis[u] = 1;
if (e[u].empty())
{
notuse[u] = 1;
return ;
}
int zero = 0, one = 0, son = 0;
for (unsigned int i = 0; i < e[u].size(); i++)
{
int v = e[u][i];
if (oncir[v])
continue;
son++;
dfs(v);
if (notuse[v] == 0)
zero++;
if (notuse[v] == 1)
one++;
}
if (zero == son)
{
if (oncir[u])
notuse[u] = 2;
else
notuse[u] = 1;
return ;
}
notuse[u] = 0;
return ;
}
void calc(int st)
{
top = 0;
findcir(st);
for (int i = 1; i <= len; i++)
dfs(seq[i]);
int zero = 0;
for (int i = 1; i <= len; i++)
{
if (notuse[seq[i]] == 0)
{
zero = i;
break;
}
}
if (zero)
{
for (int i = zero + 1; i < zero + len; i++)
{
if (notuse[seq[i]] == 0)
continue;
notuse[seq[i]] = (notuse[seq[i - 1]] ^ 1);
}
}
else
{
if (len & 1)
{
puts("-1");
exit(0);
}
for (int i = 1; i <= len; i++)
notuse[seq[i]] = (i & 1);
}
return ;
}
void solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
e[a[i]].push_back(i);
notuse[i] = -1;
}
for (int i = 1; i <= n; i++)
if (!vis[i])
calc(i);
int cnt = 0;
for (int i = 1; i <= n; i++)
cnt += notuse[i];
printf("%d\n", cnt);
for (int i = 1; i <= n; i++)
if (notuse[i])
printf("%d ", a[i]);
printf("\n");
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

D Lexichromatography

给定一个长为 n 的序列 a,你需要对这个序列进行红蓝染色。染色有如下要求:

  1. 每个位置恰好染上其中一种颜色。
  2. 对于所有的值 k,在这个序列的任意子区间 [l,r] 中,值为 k 且为红色的位置数 减去 值为 k 且为蓝色的位置数 的绝对值不超过 1。
  3. 如果按照位置顺序将所有红色的数排成序列 p,蓝色的数排成序列 q,那么 p 的字典序必须 严格大于 q 的字典序。

统计所有的合法染色方案数。对 998244353 取模。
1n,ai2×105

对于同一种数值,要求 2 等价于这些数必须红蓝交替染色。
要求字典序严格小于,一般会考虑 LCP ,但是这里有点诈骗的意味。如果一种方案红色的字典序大于蓝色的字典序,将所有数红蓝反转即可。设有 cnt 种方案使两个序列相同, m 种不同的数,那么 ans=2mcnt2.
考虑一种数值,一定是第 2k1 和第 2k 个匹配,在这两个位置间连边。如果一种数值出现奇数次, cnt=0. 如果两条边有包含关系,如 l1<l2<r2<r1 ,因为 l1r1 匹配,那么 l1+1r11 中的数和 l1 都是一个颜色,矛盾 cnt=0.
排除以上两种情况,所有线段要么相离,要么相交。相交线段限制了一些位置必须同色,它们把序列切成 x 个各自独立的块,cnt=2x. 对于 a[l1,l2], b[l2,r2], c[l3,r3],l1>l2,l1>l3 ,若 ab, ac, 则可知 bc. 于是每条线段只要考虑最后一条与它相交的线段即可。
注意,并查集连通块数等于 2x ,因为染红和染蓝各是一个块。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// #define Debug
// #define LOCAL
const int N = 2e5;
const long long P = 998244353, Inv = (P + 1) >> 1;
int n;
int a[N + 5];
int b[N + 5], m;
vector<int> pos[N + 5];
int match[N + 5];
long long ksm(long long d, long long u)
{
long long res = 1;
while (u)
{
if (u & 1)
res = res * d % P;
u >>= 1;
d = d * d % P;
}
return res;
}
int fa[N + 5];
int find(int x)
{
if (fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
void connect(int x, int y)
{
x = find(x), y = find(y);
if (x != y)
fa[x] = y;
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
b[i] = a[i];
}
m = n;
sort(b + 1, b + m + 1);
m = unique(b + 1, b + m + 1) - b - 1;
for (int i = 1; i <= n; i++)
{
a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b;
pos[a[i]].push_back(i);
}
long long ans = ksm(2, m);
for (int t = 1; t <= m; t++)
{
if (pos[t].size() & 1)
{
ans = ans * Inv % P;
printf("%lld\n", ans);
return 0;
}
int sz = pos[t].size();
for (int i = 0; i < sz; i += 2)
{
int u = pos[t][i], v = pos[t][i + 1];
match[u] = v, match[v] = u;
}
}
for (int i = 1, mx = 0; i <= n; i++)
{
mx = max(mx, match[i]);
if (match[i] > i && mx > match[i])//[i, mxt_i] \in [j, mxt_j]
{
ans = ans * Inv % P;
printf("%lld\n", ans);
return 0;
}
}
int cnt = 0;
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1, lst = 0; i <= n; i++)
{
int t = a[i];
if (match[i] < i)
{
connect(pos[t][1], i);
continue;
}
connect(pos[t][0], i);
if (i < match[lst] && match[lst] < match[i])
{
connect(i, lst);
connect(match[i], match[lst]);
}
lst = i;
}
for (int i = 1; i <= n; i++)
cnt += (find(i) == i);
cnt /= 2;
ans = (ans - ksm(2, cnt) + P) % P * Inv % P;
printf("%lld\n", ans);
return 0;
}

E Ball-Stackable

给你一棵 n 个点的树,其中有一些边方向给定,剩下的边由你来决定方向。同时你要给每条边染一个颜色。
现在有一个人在树上走,他有一个栈,当他经过一条边时会进行如下操作:

  • 如果他走的方向与边的方向相同,往栈里放一个与该边颜色相同的球。
  • 如果他走的方向与边的方向相反,从栈顶取出一个球丢掉。

一个路径合法当且仅当每次走相反的边之前栈都不为空。
你构造的方案要满足对于任意合法路径,每次走反向边时取出的球都恰好与该边颜色相同。在此基础上使边的颜色数最多,并输出方案,无解输出 -1

会不了一点 QAQ
从特殊情况入手 ,比如 所有边都没有定向 。任选一个点作根,建一棵外向树,这样所有 Stackable 的路径都不会删数,共 n1 种不同的颜色。
如果边初始有方向呢?这时有一些边是 ufa 的,我们断言只有这种边取不到新颜色。可以用换根 dp 求出以 i 为根时树上有几条反向边,取 dp 值最小的点为根即可。构造模拟就行,维护一个栈。
有一个小问题,如果某一时刻栈为空还要 pop 怎么办?此时 urt 上正向边(自上而下)比反向边(自下而上)少,总可以把 rtu 移动,只有 urt 上的边会变化,让正向边更多,然而 dprt 就不是最小的了,所以不会出现这种情况。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
const int N = 1e5, E = N << 1;
int n;
int head[N + 5], to[E + 5], nxt[E + 5], tot = 1;
int dir[E + 5];
void add_edge(int u, int v, int d)
{
tot++;
to[tot] = v;
nxt[tot] = head[u];
dir[tot] = d;
head[u] = tot;
return ;
}
void add(int u, int v, int d)//1: u -> v, 0: v -> u, 2/3: undir
{
add_edge(u, v, d);
add_edge(v, u, d ^ 1);
return ;
}
int dp[N + 5];
void dfs1(int u, int fa)
{
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (v == fa)
continue;
dfs1(v, u);
dp[u] += dp[v] + (dir[i] == 0);
}
return ;
}
void dfs2(int u, int fa)
{
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (v == fa)
continue;
int other = dp[u] - dp[v] - (dir[i] == 0);
dp[v] += other + (dir[i] == 1);
dfs2(v, u);
}
return ;
}
int stk[N + 5], top;
int cnt;
void colour(int u, int fa)
{
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (v == fa)
continue;
if (dir[i] > 1)
dir[i] = 1;
if (dir[i] == 1)
{
cnt++;
stk[++top] = cnt;
printf("%d %d %d\n", u, v, cnt);
colour(v, u);
top--;
}
else
{
int ori = stk[top];
printf("%d %d %d\n", v, u, stk[top--]);
colour(v, u);
stk[++top] = ori;
}
}
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
scanf("%d", &n);
for (int i = 1, u, v, d; i < n; i++)
{
scanf("%d%d%d", &u, &v, &d);
if (d == 0)
add(u, v, 2);
else
add(u, v, 1);
}
dfs1(1, 0);
dfs2(1, 0);
int rt = 0;
for (int i = 1; i <= n; i++)
{
if (!rt || dp[rt] > dp[i])
rt = i;
}
printf("%d\n", n - 1 - dp[rt]);
colour(rt, 0);
return 0;
}

F Indefinite Clownfish

给定一个长度为 n 的序列 a 以及正整数 k,要求从 a 中选出两个不相交的子序列,满足以下条件:

  • 第一个子序列包含的元素依次递增 1
  • 第二个子序列包含的元素依次递降 1
  • 两个子序列总长为 k
  • 两个子序列的平均值相同。
  • 设两个子序列包含的下标最大值为 r,最小值为 l,要求 rl 最小。

求上述 rl 的最小值,无解则输出 1
2kn2×105,1ain

参(搬)考(运)了官方题解

平均值相同 入手。注意到每个序列都是连续递增/递减序列,所以 ave=max+min2 ,即两个序列的 max+min 相等。进一步地,这两个序列在值域上一定相交,一定有至少一个数 x 在两个序列中都出现过,否则 ave 不会相等。这个 公共元素 地位特殊,显然是本题的切入点。
x 出现在第 p 个和第 q 个位置,不妨令 p<q 。事实上,考虑 p,q 之间只存在一段上升序列或一段下降序列的情况即可。如果这两种序列同时存在,一定会在中间产生一个新的交叉,那就从中间的重复元素入手。易知这样考虑能覆盖所有可能。中间部分上升或下降, p 属于上升 q 属于下降,或 p 属于下降 q 属于上升,于是共有 4 种情况。进一步地,在 x 所有出现位置中, p,q 一定是相邻的。如果 pos,p<pos<q,apos=x ,总能把 pq 移到 pos 处,而不会变劣。具体见下图。
CF1876F_p1.png
CF1876F_p2.png
注意 p 左侧和 q 右侧的部分,都向外延申一条上升链和一条下降链,并且这两条链在值域上不交。改写一下式子, max1+min1=max2+min2max1min2=max2min1 .再观察一下,发现 p 左侧是一个 max 和另一个 minq 右侧同理,因此只需两边的 maxmin 相等即可!又考虑到两个序列总长为 k 且序列都是连续的,有 2(maxmin+1)=k , 两侧的差均为 k22k 为奇数时无解。可以枚举四种情况,分别二分左右边界,看 p (或 q )往上和往下最大能延申到哪里,差值不小于 k22 即合法,用倍增辅助判断。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
// #define TestCases
const int N = 2e5, Lg = 18;
int n, k;
int a[N + 5];
int nxt[N + 5];
int up[N + 5][2], down[N + 5][2];//0: prefix, 1: suffix
int occ[N + 5];
int jmp[N + 5][Lg + 5][2][2];//u/d, fr/bk
int getval(int u, int ud, int fb, int tar)
{
if (fb == 0)
{
for (int k = Lg; k >= 0; k--)
{
if (jmp[u][k][ud][fb] >= tar)
u = jmp[u][k][ud][fb];
}
}
else
{
for (int k = Lg; k >= 0; k--)
{
if (jmp[u][k][ud][fb] <= tar)
u = jmp[u][k][ud][fb];
}
}
return u;
}
int ans = N + 1;
void update(int l, int r)
{
if (l != -1 && r != -1)
ans = min(ans, r - l);
return ;
}
void solve()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
if (k & 1)
return puts("-1"), void();
for (int i = 1; i <= n; i++)
{
up[i][0] = occ[a[i] + 1], down[i][0] = occ[a[i] - 1];
occ[a[i]] = i;
}
for (int i = 0; i <= n + 1; i++)
occ[i] = n + 1;
for (int i = n; i > 0; i--)
{
nxt[i] = occ[a[i]];
up[i][1] = occ[a[i] + 1], down[i][1] = occ[a[i] - 1];
occ[a[i]] = i;
}
up[0][0] = down[0][0] = 0, up[0][1] = down[0][1] = n + 1;
up[n + 1][0] = down[n + 1][0] = 0, up[n + 1][1] = down[n + 1][1] = n + 1;
for (int i = 0; i <= n + 1; i++)
{
jmp[i][0][0][0] = up[i][0], jmp[i][0][0][1] = up[i][1];
jmp[i][0][1][0] = down[i][0], jmp[i][0][1][1] = down[i][1];
}
for (int k = 1; k <= Lg; k++)
{
for (int i = 0; i <= n + 1; i++)
{
jmp[i][k][0][0] = jmp[ jmp[i][k - 1][0][0] ][k - 1][0][0];
jmp[i][k][1][0] = jmp[ jmp[i][k - 1][1][0] ][k - 1][1][0];
}
for (int i = n + 1; i >= 0; i--)
{
jmp[i][k][0][1] = jmp[ jmp[i][k - 1][0][1] ][k - 1][0][1];
jmp[i][k][1][1] = jmp[ jmp[i][k - 1][1][1] ][k - 1][1][1];
}
}
for (int i = 1; i <= n; i++)
{
int u = i, v = nxt[i], L = -1, R = -1;
if (nxt[i] > n)
continue;
int l = 1, r = u;
while (l <= r)
{
int mid = (l + r) >> 1;
int upper = getval(u, 0, 0, mid), lower = getval(u, 1, 0, mid);
if (a[upper] - a[lower] + 1 >= k / 2)
L = mid, l = mid + 1;
else
r = mid - 1;
}
l = v, r = n;
while (l <= r)
{
int mid = (l + r) >> 1;
int upper = getval(u, 0, 1, mid), lower = getval(v, 1, 1, mid);
if (a[upper] - a[lower] + 1 >= k / 2)
R = mid, r = mid - 1;
else
l = mid + 1;
}
update(L, R);
l = v, r = n, R = -1;
while (l <= r)
{
int mid = (l + r) >> 1;
int upper = getval(v, 0, 1, mid), lower = getval(u, 1, 1, mid);
if (a[upper] - a[lower] + 1 >= k / 2)
R = mid, r = mid - 1;
else
l = mid + 1;
}
update(L, R);
/*==================================================================*/
l = v, r = n, R = -1;
while (l <= r)
{
int mid = (l + r) >> 1;
int upper = getval(v, 0, 1, mid), lower = getval(v, 1, 1, mid);
if (a[upper] - a[lower] + 1 >= k / 2)
R = mid, r = mid - 1;
else
l = mid + 1;
}
l = 1, r = u, L = -1;
while (l <= r)
{
int mid = (l + r) >> 1;
int upper = getval(v, 0, 0, mid), lower = getval(u, 1, 0, mid);
if (a[upper] - a[lower] + 1 >= k / 2)
L = mid, l = mid + 1;
else
r = mid - 1;
}
update(L, R);
l = 1, r = u, L = -1;
while (l <= r)
{
int mid = (l + r) >> 1;
int upper = getval(u, 0, 0, mid), lower = getval(v, 1, 0, mid);
if (a[upper] - a[lower] + 1 >= k / 2)
L = mid, l = mid + 1;
else
r = mid - 1;
}
update(L, R);
}
if (ans > n)
ans = -1;
printf("%d\n", ans);
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

G Clubstep

Clubstep 是 Chaneka 喜爱的电子游戏中最难的之一。Clubstep 中包含 n 关,从 1n 编号。Chaneka 已经对这个游戏进行了大量练习,所以目前她对其中的第 i 关的熟悉度是 ai
之后,Chaneka 可以在 Clubstep 上进行若干次(可以是 0 次)尝试。在每一次尝试中她会在 n 关中的某一关挂掉。某次尝试在第 p 关挂掉意味着她通过了 [1,p1] 中的每一关,且没有进入 [p+1,n] 中的任何一关。一次挂在第 p 关的尝试用时 p 秒。
我们知道 Chaneka 在自己挂掉的那一关上会比其他任何一关进步更多。我们也知道在一次尝试中 Chaneka 在自己没有进入的关卡无法取得进步。所以一次挂在第 p 关的尝试的影响如下:

  • Chaneka 对第 p 关的熟悉度增加 2
  • Chaneka 对 [1,p1] 中的每一关的熟悉度增加 1

现在有 q 个询问,第 j 个询问给定三个正整数 lj,rj,xj。你需要求出 Chaneka 通过若干次尝试后对 [li,ri] 中的每一关熟悉度都不小于 xj 的最小用时(以秒为单位)。注意每次询问都是独立的,所以 Chaneka 在某次询问中的尝试不会影响其他任何一次询问的熟悉度。
n,q3105,ai,x109

做法记得补!!!111

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
// #define Debug
// #define LOCAL
// #define TestCases
const int N = 3e5, SZ = N * 40;
int n, q;
int a[N + 5];
long long ans[N + 5];
typedef pair<int, int> pir;
vector<pir> add[N + 5];
vector<int> del[N + 5];
int fa[SZ + 5], tot;
long long val[SZ + 5];
int find(int x)
{
if (fa[x] == x)
return x;
int parent = fa[x];
fa[x] = find(fa[x]);
val[x] += val[parent];
return fa[x];
}
priority_queue<pir> que;
vector<pir> tmp;
void solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
scanf("%d", &q);
for (int i = 1, l, r, x; i <= q; i++)
{
scanf("%d%d%d", &l, &r, &x);
add[r].emplace_back(x, i);
del[l].push_back(i);
}
tot = q;
for (int i = 1; i <= tot; i++)
fa[i] = i;
for (int i = n, x, id; i > 0; i--)
{
for (auto v : add[i])
que.push(v);
tmp.clear();
while (!que.empty())
{
x = que.top().first;
id = que.top().second;
if (x <= a[i])
break;
que.pop();
int tim = (x - a[i] + 1) >> 1;
val[id] += 1ll * tim * i;
x -= tim;
tmp.emplace_back(x, id);
}
int sz = tmp.size();
for (int l = 0, r = 0; l < sz; l = r + 1)
{
r = l;
while (r + 1 < sz && tmp[r].first == tmp[r + 1].first)
r++;
tot++;
fa[tot] = tot;
for (int t = l; t <= r; t++)
fa[tmp[t].second] = tot;
que.emplace(tmp[l].first, tot);
}
for (auto id : del[i])
{
find(id);
ans[id] = val[id];
}
}
for (int i = 1; i <= q; i++)
printf("%lld\n", ans[i]);
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

AtCoder Regular Contest 166

C LU / RD Marking

给一个 nm 列的网格,那么它的所有网格线上共有 n(m+1) 条竖边,(n+1)m 条横边。
有如下两种操作:

  • 选一个上面和左面的网格线都没被涂黑的格子,并涂黑这两条线;
  • 选一个下面和右面的网格线都没被涂黑的格子,并涂黑这两条线。

求执行两种操作若干次(可以为 0),可能得到不同的涂黑边集数量。
T2×105, n,m106

不会啊 QAQ QAQ
关键在与 把网格图划分为若干独立的部分 !!!
贺一张题解的图:
ARC166C_p1.png
合法的染色方案满足每一段黑色连续段的长度都是偶数。考虑 dp : dpi 表示前 i 个线段的合法方案数:
dp0=dp1=1 :都不染
dpi=dpi1+dpi2i 不染, dpi1ii1 染, dpi2
易知这是斐波那契数列,但是直接递推就行了
不妨设 nm ,就有 2 条长度为 2, 4, 6,, 2n 的线段和 mn 条长度为 2n+1 的线段。
ans=(dp2n+1)mnk=1n(dp2k)2
前面快速幂,后面预处理

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
const int N = 2e6;
const long long P = 998244353;
long long f[N + 5], g[N + 5];
long long ksm(long long d, long long u)
{
long long res = 1;
while (u)
{
if (u & 1)
res = res * d % P;
u >>= 1;
d = d * d % P;
}
return res;
}
void solve()
{
int n, m;
scanf("%d%d", &n, &m);
if (n > m)
swap(n, m);
long long ans = ksm(f[n + n + 1], m - n);
ans = ans * g[n + n] % P;
printf("%lld\n", ans);
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
f[0] = 1, f[1] = 1;
for (int i = 2; i <= N; i++)
f[i] = (f[i - 1] + f[i - 2]) % P;
g[0] = 1;
for (int i = 2; i <= N; i += 2)
g[i] = g[i - 2] * f[i] % P * f[i] % P;
int T;
scanf("%d", &T);
while (T--)
solve();
return 0;
}

AtCoder Regular Contest 167

C MST on Line++

给定正整数 n,K 和一个长度为 n 的序列 A。对于一个 1n 的排列 P,我们定义 f(P) 为以下问题的答案:

  • 给一个 n 个点的无向带权图,对于两点 i<j ,当且仅当 jiK 时,它们之间有边,边权为 max{APi,APj} 。求这个图的最小生成树边权和。

对于所有可能的排列 P,求出它们的 f(P) 之和,答案对 998244353 取模。
1KN50001Ai109

怎么 C > D 啊!阴间计数,还是对着中文题解学习舒服(
依然考虑算贡献,不妨把 {an} 从小到大排序,这样 ai<aji<j.
从最小生成树算法入手 ,本题采用经典的 Kruskal. 我们想要求出所有排列中第 i 小的边出现几次,利用容斥思想,转化为求边权前 i 小的边的出现次数,记为 fi ,则 ans=i=2nai(fifi1).
接下来求 fi. 对于一个排列,我们只关心对应的 a 是前 i 小的数在那些位置,它们具体是多少并不重要,于是设 gi 表示所有 (ni) 种选法中,一共连了几条边,继而 fi=gii!(ni)!. ( i! 表示前 i 小的数在指定位置随便选, (ni)! 表示剩下的数在剩下的位置随便选)
对于 gi ,设这 i 个位置依次为 pos1, pos2,  posi. 显然我们只用考虑在相邻的位置之间连边,这样能尽可能的满足 jik 的条件。考虑 posxposx+1 ,求 posx+1posx=k 的数量。可以先选出 i1 个位置,再在第 x 个后面插入 k 个,即 (nki1) ,现在要求 k ,则为 j=1k(nji1). 注意到 i1 个间隔两两独立,于是再乘上 i1 就是 gi 了。
综上,
{gi=(i1)j=1k(nji1)fi=gii!(ni)!ans=i=2nai(fifi1)

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
// #define TestCases
const int N = 5000;
const long long P = 998244353;
int n, k;
long long a[N + 5];
long long fac[N + 5], C[N + 5][N + 5];
void init()
{
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = fac[i - 1] * i % P;
C[0][0] = 1;
for (int i = 1; i <= n; i++)
{
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
return ;
}
long long f[N + 5], g[N + 5];
void solve()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%lld", a + i);
sort(a + 1, a + n + 1);
init();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= k; j++)
g[i] = (g[i] + C[n - j][i - 1]) % P;
g[i] = g[i] * (i - 1) % P;
}
for (int i = 1; i <= n; i++)
f[i] = g[i] * fac[i] % P * fac[n - i] % P;
long long ans = 0;
for (int i = 2; i <= n; i++)
{
long long add = a[i] * (f[i] - f[i - 1] + P) % P;
ans = (ans + add) % P;
}
printf("%lld\n", ans);
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

E One Square in a Triangle

多组测试,每次给定一个 S108 ,你需要构造一个三角形满足以下条件。

  • 该三角形的三个顶点都为格点
  • 该三角形的面积为 S2
  • 该三角形内恰好只包含一个边长为1的正方形且该正方形的顶点也为格点(正方形的边可以和三角形的边或顶点重合)

场上以为是阴间数论就弃了,赛后发现是构造,但只会一半 /youl
有人说是打表找规律,但打完表也没看出什么 /kk

分类讨论,可以把三角形平移使原点成为一个顶点
打表可知 S{1,2,3,5,7} 时无解。

S 为偶数的情况是容易的(因为我自己整出来了 233)。
ARC167E_p1.PNG
如图,以 (0,0) (0,S2) (2,2(S21)) 为顶点构造,思路是主动找一个单位正方形,并让三角形的边卡住它。
S=2 时,三角形面积为 1 ,但正方形面积已经是 1 了,必无解。

S 为奇数的情况借(照)鉴(抄)了 yx-cat 的题解 orz
A 为原点, B(xb,yb), C(xc,yc) 为另外两个顶点。由向量叉乘可知 S=|xbycybxc|. 要让 S 是奇数,则 xbycxcyb 奇偶性不同。
考虑再固定一个点,根据直觉和样例,三角形大概是倾斜放置的,即没有边与坐标轴平行,不妨令 B(3,1).
S=|3ycxc| ,要让二者奇偶性不同,令 yc=xc+1 ,这样 C(S32S12). 此时 C 在一条直线上移动,因为 AC 斜率永远大于 1 ,所以小正方形一定在三角形内( S=1/3/5/7 时,正方形在三角形外,无解)。反正看着挺对的,而且暴力验证了无解情况(
ARC167E_p2.PNG

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
void solve()
{
int s;
scanf("%d", &s);
if (s & 1)
{
if (s < 9)
puts("No");
else
printf("Yes\n0 0 3 1 %d %d\n", (s - 3) / 2, (s - 1) / 2);
}
else
{
if (s == 2)
puts("No");
else
printf("Yes\n0 0 0 %d 2 %d\n", s / 2, (s / 2 - 1) * 2);
}
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

Codeforces Round #905 (CF 1887)

A2 Dances (Hard Version)

对于每组数据,给定 nm 与数组 a 的第 2n 项和数组 b 的第 1n 项。你需要根据 a 数组求出 mc 数组的值,具体地:

  • c[i]1=i
  • c[i]j=aj(2jn)

对于每一个独立的 c[i] 数组与互不影响的 b ,你可以将 bc[i] 数组中的数字随意排序,再随意删除 c[i]b 中的 k 个数,对于每一个 c[i] 数组,求最小的 k[i] 使得1jn,c[i]jbj ,输出所有 c[i] 的删除数 k[i] 的和

赛时看见了但没注意到可以重排序列,以为是什么高妙匹配(
先考虑 m=1 的做法。把 a,b 从小到大排序,贪心考虑保留 a 的一段前缀。枚举 i ,找大于 ai 的最小 bj ,不存在就中止,这时保留的元素个数最多,也即代价最小。
推广上述做法,猜想当 a1 增大时答案最多增加 1 ,最坏无非就是把 a1 删去。因此可以二分 a1 ,找出答案为 ansans+1 的分界点即可计算,小常数 2log .

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define DeBug
// #define LOCAL
#define TestCases
const int N = 1e5;
int n, m;
int a[N + 5], b[N + 5], tmp[N + 5];
int calc(int x)
{
tmp[1] = x;
for (int i = 2; i <= n; i++)
tmp[i] = a[i];
sort(tmp + 1, tmp + n + 1);
for (int i = 1, pos = 1; i <= n; i++)
{
while (b[pos] <= tmp[i])
pos++;
if (pos > n)
return n - (i - 1);
pos++;//a_i(tmp_i) match b_pos, pos should +1
}
return 0;
}
void solve()
{
scanf("%d%d", &n, &m);
for (int i = 2; i <= n; i++)
scanf("%d", a + i);
for (int i = 1; i <= n; i++)
scanf("%d", b + i);
sort(b + 1, b + n + 1);
b[n + 1] = 0x3f3f3f3f;
int ori = calc(1);
int l = 1, r = m, pos = 1;
while (l <= r)
{
int mid = (l + r) >> 1;
if (calc(mid) == ori)
{
pos = mid;
l = mid + 1;
}
else
r = mid - 1;
}
long long ans = 1ll * ori * pos + 1ll * (ori + 1) * (m - pos);
printf("%lld\n", ans);
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

D Split

定义一个序列是好的,当且仅当它存在某种划分成左右两部分的方案,使左半部分的最大值严格小于右半部分的最小值。
现给出长度为 n 的序列 a,保证 a 是一个 1n 的排列。
q 次询问,每次询问 a 的子区间 [l,r] 是否为好的序列。
n,q3×105

补题时胡了一个在线做法,一看题解发现全是离线扫描(

Solution 1 倍增
考虑从左向右扫过区间,只需检查 max{al,al+1,,ai}<min{ai+1,ai+2,,ar} 是否成立。显然不等式左侧一定是 [l,r] 的前缀最大值,如果固定 max ,那么不等式右侧的区间长度越小越好,因为 min 不降。
为方便起见,令 an+1=
fi 表示 i 之后第一个大于 ai 的数的位置,则 l,fl,ffl, 就是 [l,r] 的所有前缀 max 位置。不妨设 p 是一个前缀 max ,只需检查 min{afp,afp+1,,ar} 是否大于 ap 。令 gi 表示最大的 j ,使 min{afi,afi+1,,aj}>ai 。条件转化为 gpr 。只要有一个 p 满足要求, [l,r] 就合法,因此只需 max{gl,gfl,gffl,}>r
f 可以倒序单调栈处理, g 可以 st 表求区间 min 配合二分解决,其余部分用倍增做也是显然的。预处理最好处理到 n+1,避免不必要的麻烦。

Solution 2 扫描线
和倍增做法本质相同。在 gi 的基础上,设 hi 表示最小的 j 使 max{aj,aj+1,,ai}=ai ,即让 ai 成为前缀 max 的最小左端点。所有 hilirgilr 的区间 [l,r] 都合法。这就是矩形加单点查,扫描线是显然的。

以下为 solution 1 代码, solution 2 代码略。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
// #define Debug
// #define LOCAL
// #define TestCases
const int N = 3e5, Lg = 18;
int n, q;
int a[N + 5];
int stk[N + 5], top;
int f[N + 5];
int st[N + 5][Lg + 5];
int g[N + 5];
int queryMin(int l, int r)
{
int k = log2(r - l + 1);
return min(st[l][k], st[r - (1 << k) + 1][k]);
}
int jmp[N + 5][Lg + 5], Max[N + 5][Lg + 5];
bool calc(int l, int r)
{
int mx = 0;
for (int k = Lg; k >= 0; k--)
{
if (jmp[l][k] > r)
continue;
mx = max(mx, Max[l][k]);
l = jmp[l][k];
}
return mx >= r;
}
void solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
a[n + 1] = N + 1;
stk[++top] = n + 1;
for (int i = n; i > 0; i--)
{
while (a[stk[top]] < a[i])
top--;
f[i] = stk[top];
stk[++top] = i;
}
for (int i = 1; i <= n + 1; i++)
st[i][0] = a[i];
for (int k = 1; (1 << k) <= n + 1; k++)
for (int i = 1; i + (1 << k) - 1 <= n + 1; i++)
st[i][k] = min(st[i][k - 1], st[i + (1 << (k - 1))][k - 1]);
for (int i = 1; i <= n; i++)
{
int l = f[i], r = n + 1;
while (l <= r)
{
int mid = (l + r) >> 1;
if (queryMin(f[i], mid) > a[i])
{
g[i] = mid;
l = mid + 1;
}
else
r = mid - 1;
}
}
f[n + 1] = g[n + 1] = n + 1;
for (int i = 1; i <= n + 1; i++)
{
jmp[i][0] = f[i];
Max[i][0] = g[i];
}
for (int k = 1; k <= Lg; k++)
for (int i = 1; i <= n + 1; i++)
{
jmp[i][k] = jmp[ jmp[i][k - 1] ][k - 1];
Max[i][k] = max(Max[i][k - 1], Max[ jmp[i][k - 1] ][k - 1]);
}
scanf("%d", &q);
for (int i = 1, l, r; i <= q; i++)
{
scanf("%d%d", &l, &r);
if (calc(l, r))
puts("Yes");
else
puts("No");
}
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

E Good Colorings

Alice 和你玩游戏。有一个 n×n 的网格,初始时没有颜色。Alice 在游戏开始前依次给其中 2n 个格子分别涂上了第 12n 种颜色,并告诉你每个颜色的位置。
接下来的每次操作,你可以选择一个未涂色的格子,由 Alice 在 2n 种颜色中选择一个涂在该格子上,并告诉你该颜色。
如果在某次操作后方格图上存在四个不同颜色的点,且它们的位置形成一个平行于边线的矩形,则输出它们以获得胜利。
你至多进行 10 次操作,请构造一个获胜方案。交互库自适应,也就是说 Alice 的决策与你的选择有关。
T200,n1000

经典 trick 的新应用(?)
很久之前教练就讲过“ 把格子看作行和列之间的边 ”的 trick ,没想到在这道题也能用!
建立一张左右各 n 个点的二分图,左侧代表行,记作 1n ;右侧代表列,记作 n+12n 。如果 (x,y) 被染了颜色 c ,就在 xy+n 之间连一条颜色为 c 的边。
目标转化为在二分图中找一个四元环且各边颜色不同。注意到原图共 2n 个点 2n 条边,并且为二分图,则一定存在一个各边颜色不同的偶环。我们随便找一个环,在中间位置连一条边(注意需要对 len=4klen=4k+2 分讨),把环切成两个长度接近的子环。无论这条新边什么颜色,两个子环中至少有一个各边颜色不同,递归下去即可。这个过程类似二分,暴力验证可以在 10 次内求出。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
const int N = 1000, M = N << 1;
int n;
vector<int> e[M + 5];
int val[N + 5][N + 5];
int seq[M + 5], len;
int stk[M + 5], top;
int vis[M + 5], dfn;
bool findcir(int u, int fa)
{
if (vis[u] == dfn)
{
while (stk[top] != u)
{
seq[++len] = stk[top];
top--;
}
seq[++len] = stk[top];
top--;
reverse(seq + 1, seq + len + 1);
if (seq[1] > n)
{
seq[len + 1] = seq[1];
for (int i = 1; i <= len; i++)
seq[i] = seq[i + 1];
}
return true;
}
stk[++top] = u;
vis[u] = dfn;
for (unsigned int i = 0; i < e[u].size(); i++)
{
int v = e[u][i];
if (v == fa)
continue;
if (findcir(v, u))
return true;
}
top--;
return false;
}
void query(int x, int y)
{
printf("? %d %d\n", x, y);
fflush(stdout);
int res;
scanf("%d", &res);
val[x][y] = res;
return ;
}
int getval(int id)
{
if (seq[id] > n)
return seq[id] - n;
return seq[id];
}
void solve()
{
scanf("%d", &n);
for (int i = 1, x, y; i <= n + n; i++)
{
scanf("%d%d", &x, &y);
e[x].push_back(y + n);
e[y + n].push_back(x);
val[x][y] = i;
}
dfn++;
for (int i = 1; i <= n; i++)
{
top = len = 0;
if (vis[i] != dfn && findcir(i, 0))
break;
}
while (len > 4)
{
int d = len >> 1;
if (len % 4 == 0)
{
int x = getval(1), y = getval(d);
query(x, y);
bool ok = true;
for (int i = 1; i < d; i++)
{
int xx = getval(i), yy = getval(i + 1);
if (i & 1)
ok &= (val[xx][yy] != val[x][y]);
else
ok &= (val[yy][xx] != val[x][y]);
}
if (ok)
len = d;
else
{
for (int i = d; i <= len; i++)
seq[i - d + 2] = seq[i];
len = d + 2;
}
}
else
{
int x = getval(1), y = getval(d + 1);
query(x, y);
bool ok = true;
for (int i = 1; i <= d; i++)
{
int xx = getval(i), yy = getval(i + 1);
if (i & 1)
ok &= (val[xx][yy] != val[x][y]);
else
ok &= (val[yy][xx] != val[x][y]);
}
if (ok)
len = d + 1;
else
{
for (int i = d + 1; i <= len; i++)
seq[i - d + 1] = seq[i];
len = d + 1;
}
}
}
printf("! %d %d %d %d\n", getval(1), getval(3), getval(2), getval(4));
fflush(stdout);
string result;
cin >> result;
for (int i = 1; i <= n; i++)
e[i].clear(), e[i + n].clear();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
val[i][j] = 0;
return ;
}
int tmp[M + 5];
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

F Minimum Segments

给定一整数序列 a1,a2,,an ,所有数字在 1n 之间,可以重复。按如下方式计算序列 a 的特征:

  • ri 为最小的 j ,满足 ai,ai+1,,aj 出现了 a 中所有数值。具体地, k[1,n]N,l[i,j]N,ak=al 。如果不存在这样的 j ,令 ri=n+1
  • a 的特征定义为序列 r

t 组数据,给出 r ,请判断是否存在一种 a 使它的特征恰好为 r ,有解需给出构造。
1t104,1n2105,n2105
i=1,2,,nirin+1

不错的题目,官方题解把 大概 思路说的很清楚。
Q:大概思路?
A:对,就是 大概 思路。
你品,你细品(

显然一个合法的 r 一定是不降的,先特判两种无解情况: r1=n+1i,ri>ri+1 。(detail++) 我也不知道为什么一定要特判,只是我的做法不判会wa,而且看江莉代码先判了这个(
考虑计算 r 的过程,我们其实不关心 a 的具体数值,只需知道有哪几种数和它们的出现位置即可。同官方题解,设 nxti 表示 ai 下一次出现的位置,不存在就设为 n+1 。根据 nxt 构造是容易的,问题转化为求 nxt
从后往前递推 nxtnxtn 必须是 n+1 ,开始分类讨论:

  • riri+1
    显然 nxti=ri+1
  • ri=ri+1
    这说明 i+1ri 中至少出现了一次 ai ,则 i+1nxtiri1 。注意这里和官方题解不同,如果 nxti=ri ,那么 ri1ri 优,矛盾。(detail++)
    nxt1,nxt2,,nxtn 中非 n+1 的值一定是两两不同的。对于 r 相同的一段,随着 i 减小, nxti 的下界降低而上界不变,则 nxti 应贪心地选择当前范围内没被占用的最大值。

但有一个例外,当 ri=ri+1=n+1 时, nxti 也可以等于 n+1 ,即 ai 是某个数值最后一次出现的位置。设 ka 数值的种类数,也即 nxtn+1 的个数,那么是否有解与 k 有关。 nxt 中有一些位置的值是确定的,剩下的由程序决定。依然考虑贪心,我们应该把最靠右的若干个 nxti 指定为 n+1 。显然 k 越大,越容易有解。
结束了吗?并没有。能构造出 nxt 只是一个必要条件。我们需要保证:一、 i>r1,j<i 满足 nxtj=i (即 r1+1n 中没有第一次出现的数值 );二、 j<r1 使 nxtj=r1 (即 r1 位置上的数必须是第一次出现的,这里官方题解又没说,detail++)。(至于为什么只判这些就够了,我也不太懂/kk)注意到随着 k 增加, nxti[r1+1,n] 的数量不增,因此 k 越小越好。
我们可以二分最小的 k ,使得可以构造出一种 nxt ,再检查上述的第一条限制。对于第二条限制,应该在构造时强制所有 nxti 不能等于 r1 (detail++),把它作为二分的判定条件或在二分后再判断都不对,因为构造错误可能使二分得到的 k 比理论最小 k 偏大或偏小。注意可能不存在合法的 k ,因此如果二分没有结果一定无解。
本题存在线性做法,比如江莉的赛时代码,但是我不会 而且懒
代码中关于二分的注释有一点问题。
Oh, I am a Chinglish man !

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
const int N = 2e5;
int n;
int r[N + 5];
int nxt[N + 5];
int used[N + 5], dfn;
int have[N + 5];
int ans[N + 5];
bool check(int k)
{
dfn++;
nxt[n] = n + 1;
for (int i = n - 1; i > 0; i--)
{
if (r[i] != r[i + 1])
nxt[i] = r[i + 1];
else
nxt[i] = 0;
used[nxt[i]] = dfn;
}
for (int i = n; i > 0; i--)
k -= (nxt[i] == n + 1);
if (k < 0)
return false;
used[r[1]] = dfn;//ensure that no nxt[i] = r[1]
for (int i = n - 1, pos = n; i > 0; i--)
{
if (nxt[i])//which means r[i] != r[i + 1]
continue;
if (r[i + 1] == n + 1 && k > 0)
nxt[i] = n + 1, k--;
else
{
pos = min(pos, r[i] - 1);//if nxt[i] == r[i], then i ~ (r[i] - 1) is ok
while (used[pos] == dfn)
pos--;
if (pos <= i)
return false;
nxt[i] = pos;
}
used[nxt[i]] = dfn;
}
return true;
}
void solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", r + i);
/*check if input is illegal*/
if (r[1] > n)
return puts("No"), void();
for (int i = 1; i < n; i++)
{
if (r[i] > r[i + 1])
return puts("No"), void();
}
/*
binary search for the smallest k which last k nxt[] is n + 1,
and there exist a possible nxt[]
note that, when k is increasing, is more likely that exist a nxt[],
and lessl likely to satisfy condition 4.
*/
int L = 1, R = n, k = -1;
while (L <= R)
{
int mid = (L + R) >> 1;
if (check(mid))
{
k = mid;
R = mid - 1;
}
else
L = mid + 1;
}
if (k == -1)
return puts("No"), void();
/*check condition 4*/
check(k);
dfn++;
for (int i = 1; i <= n; i++)
{
if (i > r[1] && have[i] != dfn)
return puts("No"), void();
have[nxt[i]] = dfn;
}
/*construct*/
for (int i = 1, cnt = 0, x; i <= n; i++)
{
if (ans[i])
continue;
cnt++;
x = i;
while (x <= n)
{
ans[x] = cnt;
x = nxt[x];
}
}
puts("Yes");
for (int i = 1; i <= n; i++)
printf("%d ", ans[i]);
puts("");
for (int i = 1; i <= n; i++)
ans[i] = 0;
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

Codeforces Round #906 (CF 1889)

B Doremy's Connecting Plan

有一张 n 个点的图,每个点有点权 ai,初始时图上没有边。
给定 c。记与点 i 连通的所有点的点权和为 Si,与点 j 连通的所有点的点权和为 Sj;若 Si+Sji×j×c,那么可以在 i,j 间连无向边。
问能否使整张图变为连通图。
n2×105,ai1012,c106

个人差略大的题目 /kk

需要从特殊情况入手 ,毕竟这是 cf div1 B ,不会上毒瘤 ds 的(。
c 没有用,因为可以把所有点的权值除以 c ,接下来不妨令 c=1 . 考虑 i 能向谁连边,希望 ij 尽量小,于是考虑 1 号点。
事实上, Si+Sjiji+j ,则 Si>iSj>j . 如果 Sii 成立,令 j=1 ,因此 Si+S11i ,即 i1 可以连边。进一步地,如果 i,j 可以连边,总可以先把一个点与 1 相连,再连另一个点,所以构造一个以 1 为根的菊花图必然不劣。
若第 i 个点与 1 连接, S1 至少为 iai ,排序后贪心即可。

Bonus:  注意到如果 1i 能连,则 j<i 都能与 i 相连,于是只要从左向右扫并且能连就连,可以做到线性。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
const int N = 2e5;
int n;
long long c;
long long a[N + 5];
long long need[N + 5];
int id[N + 5];
bool cmp(int x, int y)
{
return need[x] < need[y];
}
void solve()
{
scanf("%d%lld", &n, &c);
for (int i = 1; i <= n; i++)
{
scanf("%lld", a + i);
need[i] = c * i - a[i];
id[i] = i;
}
sort(id + 2, id + n + 1, cmp);
long long sum = a[1];
for (int i = 2; i <= n; i++)
{
if (need[id[i]] > sum)
return puts("No"), void();
sum += a[id[i]];
}
puts("Yes");
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

C2 Doremy's Drying Plan (Hard Version)

n 个点和 m 条线段,你可以选择 k 条线段删除,最大化未被线段覆盖的点的数量,输出最大值,n,m2×105,kmin(m,10)

和区间相关的 dp ,感觉是经典类型(?)

C1 中 k=2 ,可以枚举线段 + 前缀和,不要像我一样胡了一个线段树维护 dp 的做法(

C2 考虑 dp ,先将线段按右端点排序 。想法还是 刻画当前状态 ,显然不能状压,考虑 从左向右扫过序列,设法实时维护
考虑 逐个加点 。设 dpi,j 表示前 i 个点,删了 j 条线段,最多不覆盖的点数,且 i 不能被覆盖。考虑 上一个 未覆盖的点 lst ,分两种情况:

  • i 继承 lst ,即删除和 lst 相关的线段时也造成了 i 的贡献
  • i 处新删了若干线段

规定 i 处强制未覆盖的好处在于,第二种情况的线段必须不覆盖 lst ,并且 i 是这些新线段产生的第一处贡献。
因为 k 很小,所以可以预处理每个点被哪些线段覆盖。把这些线段按左端点排序,这些左端点把 1i1 切成 O(k) 段,每段需要满足的限制相同,从最大的位置转移即可。
具体地,转移为 dpi,j=1+maxlst[l,r]{dplst,jt} ,其中 [l,r] 为一小段, t 为经过 i 且未覆盖该段的区间个数。
现在要支持单点修改,区间查询最大值,开 k 棵线段树当然可行。注意到每次都是在序列后面加入新值,维护倒序的 st 表即可。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
const int N = 2e5, K = 10;
const int Lg = 17;
int n, m, k;
typedef pair<int, int> pir;
pir seg[N + 5];
int cov[N + 5], nxt[N + 5];
vector<pir> vec[N + 5];
int dp[N + 5][K + 5];
struct ST
{
int v[N + 1][Lg + 1];
void push(int pos, int val)
{
v[pos][0] = val;
for (int i = 1; (1 << i) <= pos + 1; i++)//pos + 1: index start at pos 0 !
v[pos][i] = max(v[pos][i - 1], v[pos - (1 << (i - 1))][i - 1]);
return ;
}
int query(int l, int r)
{
if (l > r)
return -N;
int k = log2(r - l + 1);
return max(v[r][k], v[l + (1 << k) - 1][k]);
}
} table[K + 1];
void solve()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1, l, r; i <= m; i++)
{
scanf("%d%d", &l, &r);
seg[i] = make_pair(l, r);
cov[l]++, cov[r + 1]--;
}
for (int i = 1, lst = 0; i <= n; i++)
{
cov[i] += cov[i - 1];
nxt[i] = lst;
if (cov[i] <= k)
lst = i;
}
for (int i = 1, l, r, pos; i <= m; i++)
{
tie(l, r) = seg[i];
pos = r;
while (pos >= l)
{
if (cov[pos] <= k)
vec[pos].emplace_back(l, r);
pos = nxt[pos];
}
}
dp[0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= k; j++)
dp[i][j] = -N;
for (int i = 1; i <= n; i++)
{
if (cov[i] > k)
{
for (int j = 0; j <= k; j++)
table[j].push(i, -N);
continue;
}
sort(vec[i].begin(), vec[i].end());
reverse(vec[i].begin(), vec[i].end());
for (int j = 0; j <= k; j++)
{
if (vec[i].empty())
{
dp[i][j] = table[j].query(0, i - 1) + 1;
continue;
}
if (vec[i][0].first < i)
dp[i][j] = table[j].query(vec[i][0].first, i - 1) + 1;
for (unsigned int t = 0; t < vec[i].size(); t++)
{
if (t + 1 != vec[i].size() &&
vec[i][t].first == vec[i][t + 1].first)
continue;
if (j < (int)t + 1)//signed +- unsigned = unsigned !!!
break;
int L = 0, R = vec[i][t].first - 1;
if (t + 1 != vec[i].size())
L = vec[i][t + 1].first;
dp[i][j] = max(dp[i][j], table[j - t - 1].query(L, R) + 1);
}
}
for (int j = 0; j <= k; j++)
table[j].push(i, dp[i][j]);
}
printf("%d\n", table[k].query(0, n));
for (int i = 1; i <= n + 1; i++)
{
cov[i] = 0;
vec[i].clear();
}
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

D Game of Stacks

function init(pos):
stacks := an array that contains n stacks r[1], r[2], ..., r[n]
return get(stacks, pos)
function get(stacks, pos):
if stacks[pos] is empty:
return pos
else:
new_pos := the top element of stacks[pos]
pop the top element of stacks[pos]
return get(stacks, new_pos)

n 个栈,每个栈里面包含若干个 1n 的数字。你想要知道 init(1n) 的值。需要注意的是,每次执行 init() 函数都会将栈复原,因此 n 次函数调用都是独立的。
n105 ,所有栈大小之和不超过 106

cf *3000 的好题,正解简单得不像这个难度的题,但是确实不好自己 get the point

可以从栈大小均为 1特例 入手,也可以从 点、环(连通性) 入手(有点像枚举做法?)。点只有 n 个,边很多且有顺序,大概是多个内向基环树叠在一起。
注意到,对于一个环,走完一圈回到起点,并且途中所有栈都弹出了一个栈顶元素,这启示可以消去一个环而不改变答案。因此只需逐个 dfs ,并实时消环。需要记忆化,因为基环树的树部分的答案都是根,多次询问叶子可以卡到 tle .

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// #define Debug
// #define LOCAL
// #define TestCases
const int N = 1e5;
int n;
vector<int> v[N + 5];
int ans[N + 5];
int stk[N + 5], top;
int instk[N + 5], dfn;
int calc(int u)
{
if (ans[u])
return ans[u];
if (instk[u] == dfn)
{
while (stk[top] != u)
{
v[stk[top]].pop_back();
instk[stk[top]] = 0;
top--;
}
v[u].pop_back();
instk[u] = 0;
top--;
}
instk[u] = dfn;
stk[++top] = u;
if (v[u].empty())
return u;
return calc(v[u].back());
}
void solve()
{
scanf("%d", &n);
for (int i = 1, sz, x; i <= n; i++)
{
scanf("%d", &sz);
while (sz--)
{
scanf("%d", &x);
v[i].push_back(x);
}
}
for (int i = 1; i <= n; i++)
{
if (ans[i])
continue;
dfn++, top = 0;
int res = calc(i);
for (int j = 1; j <= top; j++)
ans[stk[j]] = res;
}
for (int i = 1; i <= n; i++)
printf("%d ", ans[i]);
printf("\n");
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

E Doremy's Swapping Trees

考虑两张无向图 G1,G2 . G1G2 中每个点都有一个编号。称 G1G2 相似当且仅当:

  • G1 中每个节点编号互不相同, G2 中每个节点编号也互不相同。
  • G1 中所有编号组成的集合 S 恰好等于 G2 中所有编号组成的集合。
  • u,vS,uvu,v 对应的节点在 G1 中属于同一个连通块,当且仅当它们对应的节点在 G2 中属于同一个连通块。

现在给定两棵大小为 n 的树 T1,T2 ,编号均为 1n . 你可以执行如下操作任意次:

  • T1,T2 中分别选择一个边集 E1,E2 ,满足 E1E2 相似。其中, E 表示仅由 E 中的边和它们相连的点组成的图,即边导出子图。
  • T1 中的边集 E1T2 中的边集 E2 交换。

求任意次交换后有多少种不同的 T1 ,对 109+7 取模。
n105,n2105

出题人是中国人。题目不错,官方题解很烂(?),中国人能不能不坑中国人啊(
阅读题解无果,经神仙芙卡米 Imakf 点拨才明白 orz

考虑选出的边集 E1,E2 ,如果边 u,vE1 ,为了保证交换之后连通性不变,则 T2uv 路径上的所有边都必须属于 E2 ,反之同理。这有点 2-sat 的意思,我们要建出这张有向图,对它缩点,并从得到的 dag 入手。建图可以倍增优化或树剖优化,略。
如果在 dag 上选择一个点 ,则它所有能到达的点都必须被选择。一次交换就是在 dag 上选择一个这样的导出子图,将所有点中包含的边(注意这是缩点之后的图,一个“点”可以是一群点和边)和点之间的边反向。注意到,操作之后这张图还是 dag ,并不会进一步缩点。不妨把 dag 中没有出边的点称为叶子。操作之后,处于选择和未选择的分界处的点成为新的叶子。这就好比 dag 是一张 A4 纸,所有边从一端指向另一端,对折并把一侧的边全部反向,那么所有边会指向折痕处。因此,一个边集 E 可以操作,则对任意合法边集进行任意次操作后, E 依然合法。
换个角度来思考, dag 上每条边的权值初始为 0 ,操作一个点可以把一些边的权值异或 1 ,求有多少种不同的局面。感性理解一下,每个点都是线性无关的。如果按照拓扑序的顺序来决定是否操作,大概可以得到任一局面(?)。 其实我也不会严谨说明,欢迎大佬指教。 综上,如果有 dag 中共有 cnt 个点,那么 ans=2cnt .
结束了……吗?注意一种特例:一个大小为 2 的 dag 叶子。此时这两条边完全相同,交换没有任何改变,需要排除这种情况。所以只有 size>2 的点才能计入 cnt .

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
const int N = 1e5, M = N << 1;
const int Lg = 16, SIZE = M * (Lg + 1) * 2, E = N * 200;
const long long P = 1e9 + 7;
int n;
int node;
int value[SIZE + 5];
int Head[SIZE + 5], To[E + 5], Nxt[E + 5], edge = 1;
void Add_edge(int u, int v)
{
edge++;
To[edge] = v;
Nxt[edge] = Head[u];
Head[u] = edge;
return ;
}
struct Tree
{
int head[N + 5], to[M + 5], nxt[M + 5], tot = 1;
void add_edge(int u, int v)
{
tot++;
to[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
return ;
}
void add(int u, int v)
{
add_edge(u, v);
add_edge(v, u);
}
int fa[N + 5][Lg + 5], dep[N + 5];
int id[N + 5][Lg + 5];
void dfs(int u, int father, int depth)
{
fa[u][0] = father, dep[u] = depth;
id[u][0] = ++node;
value[node] = (u != 1);//root has no edge !
for (int k = 1; k <= Lg; k++)
{
fa[u][k] = fa[fa[u][k - 1]][k - 1];
id[u][k] = ++node;
Add_edge(id[u][k], id[u][k - 1]);
Add_edge(id[u][k], id[ fa[u][k - 1] ][k - 1]);
}
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (v == father)
continue;
dfs(v, u, depth + 1);
}
return ;
}
void add_path(int u, int v, int x)//x -> (u, v)
{
if (dep[u] < dep[v])
swap(u, v);
for (int k = Lg; k >= 0; k--)
{
if (dep[fa[u][k]] < dep[v])
continue;
Add_edge(x, id[u][k]);
u = fa[u][k];
}
if (u == v)
return ;
for (int k = Lg; k >= 0; k--)
{
if (fa[u][k] == fa[v][k])
continue;
Add_edge(x, id[u][k]);
Add_edge(x, id[v][k]);
u = fa[u][k], v = fa[v][k];
}
Add_edge(x, id[u][0]);
Add_edge(x, id[v][0]);
return ;
}
void clear(int len)
{
tot = 1;
for (int i = 1; i <= len; i++)
head[i] = 0;
return ;
}
} tree[2];
int dfn[SIZE + 5], low[SIZE + 5], tim;
int stk[SIZE + 5], top;
int scc, belong[SIZE + 5], sz[SIZE + 5];
int out[SIZE + 5];
void tarjan(int u)
{
dfn[u] = low[u] = ++tim;
stk[++top] = u;
for (int i = Head[u]; i; i = Nxt[i])
{
int v = To[i];
if (!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (!belong[v])
low[u] = min(low[u], dfn[v]);
else
continue;
}
if (dfn[u] == low[u])
{
scc++;
while (stk[top] != u)
{
belong[stk[top]] = scc;
sz[scc] += value[stk[top]];
top--;
}
belong[u] = scc;
sz[scc] += value[u];
top--;
}
return ;
}
void solve()
{
scanf("%d", &n);
for (int t = 0; t < 2; t++)
{
for (int i = 1, u, v; i < n; i++)
{
scanf("%d%d", &u, &v);
tree[t].add(u, v);
}
tree[t].dfs(1, 0, 1);
}
for (int t = 0; t < 2; t++)
{
for (int u = 2; u <= n; u++)
{
int v = tree[t].fa[u][0], x = tree[t].id[u][0];
tree[t ^ 1].add_path(u, v, x);
}
}
for (int i = 1; i <= node; i++)
{
if (!dfn[i])
tarjan(i);
}
for (int u = 1; u <= node; u++)
{
for (int i = Head[u]; i; i = Nxt[i])
{
int v = To[i];
if (belong[u] != belong[v])
out[belong[u]]++;
}
}
int ans = 1;
for (int i = 1; i <= scc; i++)
{
if (!sz[i] || (sz[i] == 2 && !out[i]))
continue;
ans = ans * 2 % P;
}
printf("%d\n", ans);
for (int i = 1; i <= node; i++)
{
Head[i] = 0;
belong[i] = value[i] = dfn[i] = 0;//dfn !!!
}
for (int i = 1; i <= scc; i++)
sz[i] = out[i] = 0;
edge = 1, node = tim = scc = 0;
tree[0].clear(n), tree[1].clear(n);
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

Codeforces Round #908 (CF 1893)

B Neutral Tonality

给定两个序列 {an},{bm},将 b 中所有元素以任意顺序在任意位置插入 a 中,使得形成的新序列 c 的最长上升子序列最短,输出你的序列 c
n2105,m2105

场上 wa 了一发又想不出合理做法,遂摆烂 QAQ

首先 cLIS 不会超过 aLIS+1 ,只需把 b 从大到小接在 a 后面即可。
肯定先把 b 从大到小排序。注意到,对于一段连续不升序列,最多有对 LIS1 的贡献。如果 ai>ai+1 ,可以把 bj[ai+1,ai] 的元素插入 ii+1 之间。 b>max{a}<min{a} 的数只需放在 a 前面 / 后面。
如果 ai,ai+1 中没有数在 LIS 中,则经过 bj 的新 LIS 原始长度比原始 max{LISa} 小,则加一之后至多相等。如果原始最长 LIS 经过了这两个数,插入 bj 后必然不会增加。正确性得证。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
const int N = 2e5, M = N << 1;
int n, m;
int a[N + 5], b[N + 5];
vector<int> ans;
void solve()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
for (int i = 1; i <= m; i++)
scanf("%d", b + i);
sort(b + 1, b + m + 1);
reverse(b + 1, b + m + 1);
a[0] = 1e9, a[n + 1] = 0;
for (int i = 0, pos = 1; i <= n; i++)
{
if (i > 0)
ans.push_back(a[i]);
if (a[i] > a[i + 1])
{
while (pos <= m && b[pos] <= a[i] && b[pos] >= a[i + 1])
ans.push_back(b[pos++]);
}
}
for (auto v : ans)
printf("%d ", v);
printf("\n");
ans.clear();
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

D Colorful Constructive

你有 n 个彩色的方块,其中第 i 个方块的颜色是 ai
你需要把所有方块放在架子上。总共有 m 个架子,其中第 i 个架子容量为 si,即可以放 si 个方块,且有 s1+s2++sm=n
假如有一个容量为 k 的架子上按顺序摆放了颜色分别为 c1,c2,,ck 的方块,那么定义该架子的彩色值为两个具有相同颜色的不同方块之间的最小距离。如果架子上的方块颜色各不相同,那么其彩色值为其容量 k
形式化地,摆放了颜色为 c1,c2,,ck 的方块的架子彩色值定义如下:

  • 若所有颜色 c1,c2,,ck 各不相同,则彩色值为 k
  • 否则,彩色值为最小的正整数 x 使得 i[1,kx],ci=ci+x

对于每个架子 i,有一个最小彩色值要求 di,你需要使得所有架子 i 的彩色值 di
每个测试点有 t 个测试数据。对于每个数据,你需要判断是否有一种合法的放置方块的方案满足要求。若无解,输出 1,否则输出 m 行,第 isi 个正整数表示第 i 个架子上按顺序放置的方块的颜色。
n2105

tricky problem (?)小怪兽说是贪心 + 繁琐的分讨,但其实只是简单贪心(

对于一种颜色,方块数量越少越容易满足,因为分布稀疏。显然只关心每种颜色的出现次数。可以得到一个贪心策略:对每个架子,优先放当前方块数量最多的颜色。架子可以按任意顺序枚举,对是否有解无影响。 Alex_wei 说可以调整证明,感性理解一下应该是对的。用优先队列维护,把当前填入的颜色从队列中删除,并把之前填过的且到下一个位置的距离为 d 的再次入队(如果还没用完),可以满足限制。无解在贪心过程中判断。

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
using namespace std;
// #define Debug
// #define LOCAL
#define TestCases
const int N = 2e5;
int n, m;
int a[N + 5], cnt[N + 5];
int s[N + 5], d[N + 5];
priority_queue<pair<int, int> > q;
vector<int> ans[N + 5];
void print()
{
for (int i = 1; i <= m; i++)
{
for (auto v : ans[i])
printf("%d ", v);
printf("\n");
}
return ;
}
void clr()
{
for (int i = 1; i <= n; i++)
cnt[i] = 0;
for (int i = 1; i <= m; i++)
ans[i].clear();
return ;
}
void solve()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
cnt[a[i]]++;
}
for (int i = 1; i <= m; i++)
scanf("%d", s + i);
for (int i = 1; i <= m; i++)
scanf("%d", d + i);
for (int i = 1; i <= n; i++)
{
if (cnt[i])
q.emplace(cnt[i], i);
}
bool ok = true;
for (int i = 1; i <= m && ok; i++)
{
for (int t = 0; t < s[i] && ok; t++)
{
if (q.empty())
{
ok = false;
continue;
}
auto tp = q.top();
q.pop();
cnt[tp.second]--;
ans[i].push_back(tp.second);
if (t - d[i] + 1 >= 0)
{
int col = ans[i][t - d[i] + 1];
if (cnt[col])
q.emplace(cnt[col], col);
}
}
if (!ok)
continue;
for (int t = s[i] - d[i] + 1; t < s[i]; t++)
{
int col = ans[i][t];
if (cnt[col])
q.emplace(cnt[col], col);
}
}
if (!ok)
puts("-1");
else
print();
clr();
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}

E Cacti Symphony

给定一个连通的无向图,满足其任意一个至多存在在一个简单环中。图由以下方式给出:

  • 对于给定的每条边 (x,y),额外给出一个数 d 代表这条边包含了 d 个点。即若 d0,那么这条边的实际意义应为边集 {(x,a1),(a1,a2),,(ad,y)},其中 ai 是某个新建的点。

你需要给图中的每条边和每个点赋上 13 的边权,使得:

  1. 对于每条边,其连接的两点的权值的异或和不等于 0,也不等于该边的边权;
  2. 对于每个点,与其相连的所有边的权值的异或和不等于 0,也不等于该点的点权。
    试着求出所有合法的赋权方案数对 998244353 取模的结果。

做法记得补!!!111

点击查看代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
// #define Debug
// #define LOCAL
// #define TestCases
const int N = 5e5, M = 1e6, E = M << 1;
const long long P = 998244353;
int n, m;
int sumV, sumE;
int head[N + 5], to[E + 5], nxt[E + 5], w[E + 5], tot = 1;
void add_edge(int u, int v, int d)
{
tot++;
to[tot] = v;
nxt[tot] = head[u];
w[tot] = d;
head[u] = tot;
return ;
}
void add(int u, int v, int w)
{
add_edge(u, v, w);
add_edge(v, u, w);
return ;
}
int dfn[N + 5], low[N + 5], tim;
int stk[N + 5], top;
int cnt, edcc[N + 5];
void tarjan(int u, int fr)
{
dfn[u] = low[u] = ++tim;
stk[++top] = u;
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if ((i ^ 1) == fr)
continue;
if (!dfn[v])
{
tarjan(v, i);
low[u] = min(low[u], low[v]);
}
else
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u])
{
cnt++;
while (stk[top] != u)
{
edcc[stk[top]] = cnt;
top--;
}
edcc[u] = cnt;
top--;
}
return ;
}
long long sz[N + 5];
long long ksm(long long d, long long u)
{
long long res = 1;
while (u)
{
if (u & 1)
res = res * d % P;
u >>= 1;
d = d * d % P;
}
return res;
}
const long long TwoThird = 2ll * ksm(3, P - 2);
void solve()
{
scanf("%d%d", &n, &m);
sumV = n & 1, sumE = 0;//sumE = 0 !!!
for (int i = 1, u, v, d; i <= m; i++)
{
scanf("%d%d%d", &u, &v, &d);
add(u, v, d);
sumV = sumV ^ (d & 1);
sumE = sumE ^ ((d & 1) ^ 1);
}
if (sumV != sumE)
return puts("0"), void();
long long ans = 1;
tarjan(1, 0);
for (int u = 1; u <= n; u++)
{
sz[edcc[u]]++;
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (v > u)
continue;
if (edcc[u] == edcc[v])
sz[edcc[u]] += w[i];
else
{
ans = ans * ksm(TwoThird, w[i] + 1) % P;
ans = ans * ksm(3, w[i]) % P;
}
}
}
for (int i = 1; i <= cnt; i++)
{
if (sz[i] == 1)
{
ans = ans * 3ll % P;
continue;
}
long long multi = ksm(2, sz[i]);
if (sz[i] & 1)
multi = (multi + P - 2) % P;
else
multi = (multi + 2) % P;
ans = ans * 2ll % P * multi % P;
}
printf("%lld\n", ans);
return ;
}
int main()
{
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("mycode.out", "w", stdout);
#endif
int T = 1;
#ifdef TestCases
scanf("%d", &T);
#endif
while (T--)
solve();
return 0;
}
posted @   yhk1001  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示