2020 8,9月做题记录

CF1076F Summer Practice Report

题目描述:给定 \(n\) 个字符串,第 \(i\) 个包含 \(x_i\)a\(y_i\)b,将它们按顺序首尾相接,求是否有可能不存在连续 \(k\) 个字符相同。

数据范围:\(n\le 3\times 10^5,x_i,y_i,k\le 10^6\)

直接贪心即可,每个字符串都是一种字符插入另一种字符中。

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
const int N = 300003;
int n, k; LL x[N], y[N];                            
int main(){
	read(n); read(k);
	for(Rint i = 1;i <= n;++ i) read(x[i]);
	for(Rint i = 1;i <= n;++ i) read(y[i]);
	LL tmp = 0;
	for(Rint i = 1;i <= n;++ i){
		tmp = max(0ll, x[i] + tmp - k * y[i]);
		if(tmp > k) return puts("NO"), 0;
	} tmp = 0;
	for(Rint i = 1;i <= n;++ i){
		tmp = max(0ll, y[i] + tmp - k * x[i]);
		if(tmp > k) return puts("NO"), 0;
	} puts("YES");
}

CF919F A Game With Numbers

题目描述:Alice 和 Bob 在玩♂游戏,他们分别有 8 张卡片,每张卡片上有 \([0,4]\) 的数字。两人轮流操作,Alice 先手,每次取自己和对方各一张牌 \(a,b\),要求 \(ab\ne 0\),然后将 \(a\) 变为 \((a+b)\bmod 5\)。先将牌变为全 \(0\) 的人获胜,问最终的胜负情况。\(T\) 组数据。

数据范围:\(T\le 10^5\)

直接暴力枚举所有状态并连边即可...

-CF930E Coins Exhibition

题目描述:问有多少个长为 \(k\)\(01\) 字符串,满足 \(n+m\) 个限制。有 \(n\) 个形如 \([l_i,r_i]\) 不为全 \(0\),有 \(m\) 个形如 \([l_i,r_i]\) 不为全 \(1\)。答案对 \(10^9+7\) 取模。

数据范围:\(k\le 10^9,n,m\le 10^5\)

先对所有的 \(l_i-1\)\(r_i\) 离散化。

大概...直接容斥+dp?

-CF933E A Preponderant Reunion

题目描述:给定长为 \(n\) 的自然数序列,每次你可以选择两个相邻的正整数 \(p_i,p_{i+1}\),然后将它们都减去 \(\min(p_i,p_{i+1})\)。问最小的操作次数使不能再操作,并构造方案。

数据范围:\(n\le 3\times 10^5,p_i\le 10^9\)

CF891E Lust

题目描述:给定 \(n\) 个数 \(a_1,a_2,\dots,a_n\),进行 \(k\) 次操作,每次随机选择一个数 \(x\in[1,n]\),然后将 \(a_x\) 减去 \(1\),将答案加上其他所有数的乘积,求答案期望\(\bmod(10^9+7)\)

数据范围:\(n\le 5000,a_i,k\le 10^9\)

容易发现答案就是初始状态乘积减去结束状态乘积。

现在要计算的就是

\[\begin{aligned} &k![x^k]\prod_{i=1}^n\sum_j\frac{a_i-j}{j!}x^j \\ =&k![x^k]\prod_{i=1}^n(a_i\sum_j\frac{1}{j!}x^j-\sum_j\frac{x^j}{(j-1)!}) \\ =&k![x^k]\prod_{i=1}^n(a_i-x)e^x \\ =&k![x^k]e^{nx}\prod_{i=1}^n(a_i-x) \end{aligned} \]

\(\prod_{i=1}^n(a_i-x)=\sum_{i=0}^nf_ix^i\),则这个柿子的值为 \(\sum_{i=0}^nf_in^{k-i}k^{\underline{i}}\)。时间复杂度 \(O(n\log^2n)\)

CF819E Mister B and Flight to the Moon

题目描述:给定 \(n\) 个点的无向完全图,求用长为 \(3,4\) 的环将每条边恰好覆盖 \(2\) 次的方案。

数据范围:\(n\le 300\)

首先考虑 \(n\) 是奇数的情况,可以先不管 \(1\) 号点,然后将剩下的点两两配对。先从 \(1\) 连出一些三角形把配对的边去掉,然后再考虑每两对之间的 \(4\) 条边,用两个长度为 \(4\) 的环覆盖即可。

然后考虑 \(n\) 是偶数的情况,不管 \(1,2,3,4\) 号点,将剩下的点两两配对。每两对之间的边用同样的方法即可,然后考虑 \(1,2,3,4\) 号点的连边,用 \(3\) 个长度为 \(4\) 的环覆盖即可。然后再考虑 \(1,2,3,4\) 与其他组的连边。先用 \((1,i,i+1)\)\((2,i,i+1)\) 解决 \((i,i+1)\) 的边,然后再用 \(2\) 个长度为 \(4\) 的环覆盖。具体实现可以看代码,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n;
int main(){
	read(n);
	if(n & 1){
		printf("%d\n", (n * n - 1) >> 2);
		for(Rint i = 2;i < n;i += 2){
			printf("3 1 %d %d\n", i, i + 1);
			printf("3 1 %d %d\n", i, i + 1);
			for(Rint j = 2;j < i;j += 2){
				printf("4 %d %d %d %d\n", i, j, i + 1, j + 1);
				printf("4 %d %d %d %d\n", i, j, i + 1, j + 1);
			}
		}
	} else {
		printf("%d\n", (n * n >> 2) - 1);
		puts("4 1 2 3 4"); puts("4 1 3 4 2"); puts("4 1 4 2 3");
		for(Rint i = 5;i < n;i += 2){
			printf("3 1 %d %d\n", i, i + 1);
			printf("3 2 %d %d\n", i, i + 1);
			printf("4 1 %d 2 %d\n", i, i + 1);
			for(Rint j = 3;j < i;j += 2){
				printf("4 %d %d %d %d\n", i, j, i + 1, j + 1);
				printf("4 %d %d %d %d\n", i, j, i + 1, j + 1);
			}
		}
	}
}

CF933D A Creative Cutout

AGC047E Product Simulation

本题是提交答案题

题目描述:使用不超过 \(2\cdot10^5\) 个值域为 \([0,10^{19}]\) 的整型,和不超过 \(2\cdot10^5\) 个操作,实现两个 \([0,V]\) 的整数 \(a[0],a[1]\) 的乘法。每次操作形如 \(a[k]=a[i]+a[j]\)\(a[k]=a[i]<a[j]\)

数据范围:部分分 \(V=10\),满分 \(V=10^9\)

不知为何,首先考虑将这两个数进行二进制拆分。

首先考虑如何算出 \(1\),可以用 \((0<a[0]+a[1])\),因为如果 \(a[0]=a[1]=0\) 的话无法做出非 \(0\) 数,所以是没有关系的。然后就可以加法堆到 \(2^k(k\in[0,30))\)

然后考虑 \(a[k]=\max(a[i]-a[j],0)\),主要想法就是将 \(a[j]\) 不断抬高至 \(a[i]\) 的位置,看看抬高了多少,可以用倍增实现。二进制拆分也是倍增,从大到小枚举每一位然后减去它即可。

拆分之后,由于每一位都是 \(0\)\(1\),所以可以用 \(1<(a+b)\) 计算 \(ab\),暴力卷积即可,时间复杂度 \(O(\log^2V)\)

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const int N = 200000;
int _ = 2, pw[30], a2[30], b2[30];
LL a[N];
int COMP(int x, int y){
	int k = ++_; a[k] = a[x] < a[y];
	printf("< %d %d %d\n", x, y, k);
	return k;
}
int ADD(int x, int y){
	int k = ++_; a[k] = a[x] + a[y];
	printf("+ %d %d %d\n", x, y, k);
	return k;
}
void ADDTO(int x, int y){
	printf("+ %d %d %d\n", x, y, x);
	a[x] += a[y];
}
void CALC(int a, int *a2){
	int now = ++_; ADDTO(a, pw[0]);
	for(int i = 29;~i;-- i){
		a2[i] = COMP(ADD(now, pw[i]), a);
		if(i){
			int k = ADD(a2[i], a2[i]);
			for(int j = 1;j < i;++ j) ADDTO(k, k);
			ADDTO(now, k);
		}
	}
}
int main(){
	freopen("cat.txt", "w", stdout);
	scanf("%llu%llu", a, a+1); pw[0] = COMP(2, ADD(0, 1));
	for(int i = 1;i < 30;++ i) pw[i] = ADD(pw[i-1], pw[i-1]);
	CALC(0, a2); CALC(1, b2);
	for(int k = 58;~k;-- k){
		for(int i = max(0, k - 29);i <= 29 && i <= k;++ i)
			ADDTO(2, COMP(pw[0], ADD(a2[i], b2[k-i])));
		if(k) ADDTO(2, 2);
	} cerr << a[2] << endl;
}

CF1305G Kuroni and Antihype

题目描述:有 \(n\) 个人要加入 CCF,每个人有一个权值 \(a_i\),两个人 \(i,j\) 是朋友当且仅当 \(a_i\ \text{AND}\ a_j=0\)。每次第 \(i\) 个人可以主动加入 CCF,或者已经加入 CCF 的人邀请他的一个朋友加入 CCF 并得到 \(a_i\) 元钱。求总共至多可以 qj CCF 多少钱。

数据范围:\(n,a_i\le 2\times 10^5\)

我们可以先设一个 dzd,这个人是所有人的朋友且一开始就加入了 CCF。设一条边的边权是 \(w_{i,j}=a_i+a_j\),答案就是边权之和-点权之和,问题就是求解最大生成树。

直接做 Kruskal,时间复杂度 \(O(3^{18}\alpha(n))\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 300000, m = 1 << 18, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, a[N], fa[N]; LL ans;
int getfa(int x){while(x != fa[x]) x = fa[x] = fa[fa[x]]; return x;}
void comb(int x, int y, int val){
	if(a[x] && a[y]){
		x = getfa(x); y = getfa(y);
		if(x != y){ans += (a[x] + a[y] - 1ll) * val; fa[x] = y; a[y] = 1;}
	}
}
int main(){
	read(n); a[m] = 1;
	for(Rint i = 0;i <= m;++ i) fa[i] = i;
	for(Rint i = 1, x;i <= n;++ i){read(x); ++ a[x]; ans -= x;}
	for(Rint i = m - 1;~i;-- i){
		for(Rint j = i;j;j = (j - 1) & i) comb(j, i ^ j, i);
		comb(i, m, i); comb(0, i, i);
	}
	printf("%lld\n", ans);
}

CF1290E Cartesian Tree

题目描述:给定长为 \(n\) 的排列 \(a\),对于每个 \(k\in[1,n]\),将排列中 \(\le k\) 的项构成的子序列建大根笛卡尔树,求所有节点的子树大小之和。

数据范围:\(n\le 1.5\times10^5\)

答案可以用 \(\sum(r_i-l_i-1)\) 表示,其中 \(l_i\) 表示 \(i\) 左边第一个 \(>a_i\) 的,\(r_i\) 表示 \(i\) 右边第一个 \(>a_i\) 的。

CF1336D Yui and Mahjong Set

这是一道交互题

题目描述:交互器有一个可重集 \(S\),保证元素属于 \([1,n]\),且每种元素出现次数不超过 \(n\)。定义一个 \(\mathbf{triplet}\)\(S\) 的大小为 \(3\) 的子集,且这三个元素的值相等。定义一个 \(\mathbf{straight}\)\(S\) 的大小为 \(3\) 的子集,且这三个元素的值连续。你可以进行至多 \(n\) 次查询,每次查询你给出一个数 \(x\in[1,n]\),交互器告诉你目前 \(\mathbf{triplet}\)\(\mathbf{straight}\) 的数量,然后将 \(x\) 插入集合 \(S\)。你需要求出初始集合中,对于每个 \(i\in[1,n]\)\(i\) 的出现次数。

数据范围:\(4\le n\le 100\)

UOJ549 【UNR #4】序列妙妙值

题目描述:给定长为 \(n\) 的自然数序列 \(\{a_i\}\) 和正整数 \(k\),对于所有 \(i\in[k,n]\cap\Z\),求将 \(a_1,a_2,\dots,a_i\) 分为 \(k\) 段,每段异或和之和的最小值。

数据范围:\(k\le n\le 6\times10^4,k\le 8,a_i<2^{16}\)

跟 ntf 的 sd 题撞 idea 复杂度还被吊打...

\(O(nkv)\) 的暴力 dp 很容易做。但是每一个二进制位是互相独立的,使用一个辅助的数据结构,将 dp 赋值和求 \(\min\) 抽象为修改和查询,修改时枚举高 \(8\) 位更新,查询时枚举低 \(8\) 位求 \(\min\),就平衡了复杂度,时间复杂度 \(O(nk\sqrt v)\)

UOJ551 【UNR #4】校园闲逛

题目描述:给定一个 \(n\times n\) 的矩阵 \(A\),每个元素是一个次数不超过 \(\max_v\) 的多项式。\(q\) 次询问 \(x,y,v\),求 \([z^v]A^{-1}_{x,y}\bmod 998244353\)

数据范围:\(n\le 8,v\le\max_v\le 65000,q\le 10^4\)

CF1394

B Boboniu Walks on Graph

题目描述:给定 \(n\) 个点 \(m\) 条边的有向图,每个点的出度至多为 \(k\),一个长为 \(k\) 的序列 \((c_1,c_2,\dots,c_k)\) 为合法的当且仅当:

  1. \(1\le c_i\le i\)
  2. 从任意节点 \(u\) 开始行走,每次若当前节点的出度为 \(i\),则选择连出的边中编号第 \(c_i\) 小的走,可以在有限时间内回到 \(u\)

求有多少个合法的序列。

数据范围:\(n,m\le 2\cdot10^5,k\le 9\)

如果满足题目要求,那么最终一定会成一个大环。所以序列合法当且仅当 \(\{nxt_{i,c_{deg_i}}|i\in[1,n]\cap\Z\}=[1,n]\cap\Z\),设 \(S_{i,j}=\{nxt_{u,j}|deg_u=i\}\),则变为 \(\bigcup_{i=1}^nS_{i,c_i}=[1,n]\cap\Z\),并且这些集合必定不相交,所以可以用 hash 判断,时间复杂度 \(O(n+m+k!)\)

C Boboniu and String

题目描述:对于一个 \(01\) 字符串 \(s\),你可以进行如下操作:

  1. 删除任意一个字符;

  2. 删除任意一个 \(01\)\(10\)

  3. 在末尾加一个字符;

  4. 在末尾加一个 \(01\)\(10\)

两个字符串 \(s,t\) 相似当且仅当 \(|s|=|t|\)\(0\) 的出现次数相等。\(\text{dist}(s,t)\) 是使两个字符串的编辑距离。

给定 \(n\) 个字符串 \(s_i\),你可以选择任意的 \(t\),使得 \(\max_{i=1}^n\text{dist}(s_i,t)\) 最小。求出最小值并构造任意一个 \(t\)

数据范围:\(n\le 3\cdot10^5,\sum|s_i|\le 5\cdot10^5\)

D Boboniu and Jianghu

题目描述:给定 \(n\) 个点的树,每个点有权值 \(h_i,t_i\),你要将边集分割成一些路径,满足每条路径上的 \(h\) 单调递增,一条路径的权值为上面所有点的 \(t\) 之和,求所有路径权值和的最小值。

数据范围:\(n\le2\cdot10^5,1\le h_i,t_i\le 10^6\)

E Boboniu and Banknote Collection

题目描述:给定一个长为 \(n\) 的序列 \(a_i\),问你最多可以把它沿两个数之间的分界线对折多少次,使得相同位置的数相同,并构造一种方案。

形式化定义:设 \(b\) 是一个长为 \(n\) 的序列,称 \(b\)\(a\) 的折叠序列当且仅当

  1. \(b_i=\pm 1\).
  2. \(p_i=[b_i=1]+\sum_{j=1}^{i-1}b_j\),则 \(p_i=p_j\Rightarrow a_i=a_j\)

\(b\) 的权值 \(f(b)=\sum_{i=1}^{n-1}[b_i\ne b_{i+1}]\)。求 \(F(a)=\max\{f(b)|b\text{ is a folding sequence of }a\}\)

数据范围:\(1\le a_i\le n\le 10^5\)

UOJ553 【UNR #4】己酸集合

题目描述:给定平面上 \(n\) 个点,\(q\) 次询问与 \((0,z)\) 距离不超过 \(r\) 的有多少个点。

数据范围:\(n\le 1.2\times 10^4,q\le 10^6\)

\(x^2+(y-z)^2\le r^2\)\(x^2+y^2\le r^2-z^2+2zy\),则将 \((x,y)\) 映射到 \((y,x^2+y^2)\),则每次询问为一条直线 \(y=r^2-z^2+2zx\) 以下有多少个点。

这个题在 ZR 集训讲过,可以用分块的方法,将所有点平均分成 \(S\) 块,每块维护所有 \(k\)\(y-kx\) 排序之后的顺序,这种顺序只有平方个,处理询问的时候二分即可,时间复杂度 \(O((\frac{n^2}{S}+qS)\log n)\),取 \(S=\frac{n}{\sqrt q}\) 得到 \(O(n\sqrt q\log n)\)

CF1392H ZS Shuffles Cards

题目描述:给定 \(n+m\) 张扑克牌,其中有 \(n\) 个编号 \([1,n]\cap\Z\),有 \(m\) 个 Joker。你有一个初始为空的集合 \(S\),一开始你把这些牌随机洗好,并进行以下操作:摸一张牌,

  • 如果这张牌有编号 \(x\),那么他会拿走这张牌并将 \(x\) 加入集合 \(S\)

  • 如果这张牌是 Joker,他会把所有牌(包括已经拿走的)放在一起,随机洗好。若此时 \(S=[1,n]\cap\Z\) 则停止游戏。

求期望时间\(\bmod 998244353\)

数据范围:\(n,m\le 2\times10^6\)

思考一下,发现抽到鬼牌之间的摸牌情况是独立的,所以答案就是 期望轮数*每轮的期望时间。

每轮的期望时间就是:

\[E[X]=\frac{(n+m)!}{n!}\sum_{i=0}^n\frac{i!}{(i+m)!} \]

Codechef Challenge AUG20

SUBSFREQ

题目描述:给定一个长为 \(n\) 的正整数序列 \(a_i\),求对于所有 \(x\in[1,n]\cap\Z\),求 \(x\)\(\{a\}\) 的所有非空子序列的众数(出现次数相同的取较小的)中出现多少次。\(T\) 组数据。

数据范围:\(T\le 100,\sum n\le 5\cdot10^5,1\le a_i\le n\)

PSTONES

题目描述:给定 \(n\) 个点的树,边有 \(k\) 种颜色。对于所有 \(2^k\) 种颜色集合 \(S\)\(m\in[1,n]\cap\Z\),你需要判断是否存在一个大小为 \(m\) 的连通子集,使得连接这个联通子集和联通子集之外的点的所有边的颜色集合为 \(S\)\(T\) 组数据。

数据范围:\(\sum n\le 500,k\le 12\)

NOI2020

D1T1 美食家

题目描述:给定 \(n\) 个点 \(m\) 条边的有向图,经过第 \(i\) 条边 \((u_i,v_i)\) 需要 \(w_i\) 的时间,第 \(i\) 个点有 \(c_i\) 的权值。还有 \(k\) 个三元组 \((t_i,x_i,y_i)\),表示在 \(t_i\) 时刻第 \(x_i\) 个点的权值会加上 \(y_i\)。一个包含第 \(1\) 个点的环的权值为,你在 \(0\) 时刻从第 \(1\) 个点出发,沿着这个环走,沿途遇到的所有点的权值之和。求环权值的最大值。

数据范围:\(n\le 50,n\le m\le 501,k\le 200,t_i\le 10^9,w_i\le 5,c_i\le 52501\)

D1T2 命运

题目描述:给定 \(n\) 个点的以 \(1\) 为根的树,和 \(m\) 个点对 \((u_i,v_i)\),满足 \(u_i\ne v_i\)\(u_i\)\(v_i\) 的祖先。求有多少种给边黑白染色的方法,使得所有 \(u_i\)\(v_i\) 的路径上必有黑边。

数据范围:\(n,m\le 5\times10^5\)

\(dp_{u,i}\) 表示考虑 \(u\) 的子树,\(u\) 往上最近的黑边的深度为 \(i\) 的方案数。

\[dp_{u,i}=\prod_{(u,v)\in E}(dp_{v,dep_v}+dp_{v,i}) \]

D1T3 时代的眼泪

题目描述:给定长为 \(n\) 的排列 \(p\)\(m\) 次询问,每次询问给出 \((r_1,r_2,c_1,c_2)\),表示在所有 \(r_1\le i\le r_2,c_1\le p_i\le c_2\) 的位置中,有多少个顺序对。

数据范围:\(n\le10^5,m\le2\times10^5\)

D2T1 制作菜品

题目描述:给定正整数 \(n,m,k\)\(n\) 个正整数 \(d_i\),满足 \(\sum d_i=mk\)。每次操作,你可以使 \(d\) 中 1 或 2 个数的值变小,且变化量之和为 \(k\)。求给出 \(m\) 次操作使 \(d_i=0\) 的一种方案或判断无解。\(T\) 组数据。

数据范围:\(T\le 10,n\le 500,n-2\le m\le 5000,k\le 5000\)

D2T2 超现实树

题目描述:此处定义的”树“指非空有根,区分左右儿子的二叉树,这样的所有树构成的集合称为 \(U\)。定义一棵树 \(T\) 的生长集合 \(\text{grow}(T)\) 为每次可以将 \(T\) 的一个叶子变为一棵树,得到的所有树构成的集合。定义一个树集 \(\mathcal F=\{T_1,T_2,\dots,T_n\}\) 的生长集合 \(\text{grow}(\mathcal F)=\bigcup_{i=1}^n\text{grow}(T_i)\)。每次给定一个树集 \(\mathcal F\),判断 \(\complement_U\text{grow}(\mathcal F)\) 是否为有限集。\(T\) 组数据。

数据范围:\(T\le 100,\sum n\le 2\times10^6\)

D2T3 翻修道路

题目描述:给定 \(n\) 个点 \(m\) 条边的边带权的弦图和两个端点 \(s,t\),你要找到一条 \(s\)\(t\) 之间的路径,使得去掉这个路径上面的边时,图仍然联通,求这条路径上边的权值和的最小值,或判断无解。

数据范围:\(n\le 5\times 10^5,m\le 10^6\)

P5405 [CTS2019]氪金手游

题目描述:给定 \(n\) 个点 \(n-1\) 条边的有向图,它对应的边集相同的无向图是树。第 \(i\) 个点有一个权值 \(w_i\),它有 \(j\) 的概率是 \(p_{i,j}\),其中 \(j\in\{1,2,3\}\)。每次以 \(w_i\) 的权重随机选一个没有选过的点打上标记,问标记顺序是合法拓扑序的方案数。

数据范围:\(n\le 10^3\)

当这棵树是以 \(1\) 为根的外向树时,设 \(sz_i\) 表示以 \(i\) 为根的子树内的点权之和,则答案为 \(\prod\frac{w_i}{sz_i}\),若有内向边则容斥,时间复杂度 \(O((nv)^2)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 1003, M = 3003, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b = mod - 2){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, head[N], to[N<<1], nxt[N<<1], f[N][M], g[M], inv[M], siz[N], ans;
void add(int a, int b){
	static int cnt = 0;
	to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt;
}
void init(int m){
	inv[1] = 1;
	for(Rint i = 2;i <= m;++ i) inv[i] = mod - (LL) mod / i * inv[mod % i] % mod;
}
void dfs(int x, int fa = 0){
	siz[x] = 3;
	for(Rint i = head[x];i;i = nxt[i]) if(to[i] != fa){
		dfs(to[i], x);
		for(Rint j = 1;j <= siz[x];++ j)
			for(Rint k = 1;k <= siz[to[i]];++ k){
				int val = (LL) f[x][j] * f[to[i]][k] % mod;
				if(i & 1) qmo(g[j+k] += val - mod);
				else {qmo(g[j] += val - mod); qmo(g[j+k] -= val);}
			}
		siz[x] += siz[to[i]];
		for(Rint j = 1;j <= siz[x];++ j) f[x][j] = g[j], g[j] = 0;
	}
	for(Rint j = 1;j <= siz[x];++ j) f[x][j] = (LL) f[x][j] * inv[j] % mod;
}
int main(){
	read(n); init(3*n);
	for(Rint i = 1, a[4];i <= n;++ i){
		read(a[0]); read(a[1]); read(a[2]); a[3] = ksm(a[0] + a[1] + a[2]);
		for(Rint j = 1;j <= 3;++ j) f[i][j] = (LL) j * a[j-1] * a[3] % mod;
	}
	for(Rint i = 1, u, v;i < n;++ i){
		read(u); read(v); add(u, v); add(v, u);
	}
	dfs(1);
	for(Rint i = 1;i <= siz[1];++ i) qmo(ans += f[1][i] - mod);
	printf("%d\n", ans);
}

-CF1060F Shrinking Tree

题目描述:给定 \(n\) 个点的树,每次等概率选取一条边,合并这条边连接的两个点,新点的编号等概率选取这两个节点之一,直到只剩下一个节点。对于 \(i\in[1,n]\cap\Z\),求出最后得到的点的编号为 \(i\) 的概率。

数据范围:\(n\le 50\)

-CF794E Choosing Carrot

题目描述:给定长为 \(n\) 的序列 \(a_i\),Alice 和 Bob 在玩游戏,一次操作删掉序列的开头或结尾,直到剩下唯一一个数,Alice 想让这个数尽量大,Bob 想让这个数尽量小,对于 \(k\in[1,n]\cap\Z\),若 Alice 先进行 \(k\) 次操作,Bob 再操作,之后轮流操作,求最后剩下的数。

数据范围:\(n\le 3\times 10^5,a_i\le 10^9\)


csl

UOJ429 【集训队作业2018】串串划分

题目描述:给定长为 \(n\) 的字符串 \(S\),你要将它划分若干个子串使得 \(s_i\ne s_{i+1}\),且 \(s_i\) 的最短循环节是自己。求划分方案数\(\bmod 998244353\)

数据范围:\(n\le 2\times 10^5\)

CF356E Xenia and String Problem

题目描述:给定长为 \(n\) 的字符串 \(S\),定义一个字符串 \(T\) 合法当且仅当 \(T\) 的长度为奇数,且中间的字符只出现一次,且左右两边都是合法串或空串。求在修改 \(S\) 的一个字符的情况下,\(S\) 的所有合法子串的长度的平方和的最大值。

数据范围:\(n\le 10^5\)

CF gym 100221C Forbidden Subwords

题目描述:给定一个字符串集合 \(\mathcal F\),求有多少个两边无限长的字符串 \(\alpha\),使得 \(\mathcal F\) 中的每个字符串 \(S\) 都不是 \(\alpha\) 的子串。两个两边无限长的字符串 \(\alpha=\beta\) 当且仅当 \(\exist k\in\Z,\forall i\in\Z,\alpha_{i}=\beta_{i+k}\)。需判断无解。

数据范围:\(|\mathcal F|\le 10^3,|S|\le 10,|\Sigma|\le 6\)

根据 AC 自动机,可以建立出一个转移图,这个图上的任意一条路径对应的一个字符串均合法。

分析得出,如果这个图上有一个强联通分量不是简单环,那么无解。然后给强联通分量缩点,如果缩点之后有一条路径包含三个简单环,那么无解。

于是合法的字符串就只能是一个环到另一个环的路径构成的,直接计数即可。

UOJ499 新年的邀请函

题目描述:给定正整数 \(n,m\)。已知质数 \(p\in[1,m]\),给定 \(10^5\) 个限制 \(a_i,t_i\),表示 \(a_i^{\frac{p-1}{2}}\equiv t_i(\text{mod} \ p)\)。求满足要求的质数 \(p\),保证有唯一解。

数据范围:\(1\le a_i\le n\le 10^9,m\le 10^{12}\)\(a_i\)\(p\) 在值域范围内等概率随机。

大约有 \(400\) 个数仅包含前 \(25\) 个质数,由于勒让德符号的积性,可以高斯消元求出前 \(25\) 个质数\(\bmod p\) 是否为二次剩余。根据二次互反律,可以得出 \(p\) 模前 \(25\) 个质数是否为二次剩余。

HDU5451 Best Solver

题目描述:给定自然数 \(x\),质数 \(p\),计算 \(\lfloor(5+2\sqrt 6)^{1+2^x}\rfloor\) 对质数 \(p\) 取模,\(T\) 组数据。

数据范围:\(x<2^{32},p\le 46337,T\le 10^3\)

直接暴力扩域 \((\Z/p\Z)[\sqrt 6]\),则 \((5+2\sqrt 6)^n\) 有周期 \(p^2-1\),直接将 \(1+2^x\)\(p^2-1\) 取模再计算即可。时间复杂度 \(O(\log x+\log p)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
int t, x, p, mod;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & p;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
struct Node {
	int a, b;
	Node(int _a = 0, int _b = 0): a(_a), b(_b){}
	Node operator * (const Node &o) const {return Node(((LL) a * o.a + 6ll * b * o.b) % p, ((LL) a * o.b + (LL) b * o.a) % p);}
};
Node ksm(Node a, int b){
	Node res(1);
	for(;b;b >>= 1, a = a * a) if(b & 1) res = res * a;
	return res; 
}
int solve(){
	read(x); read(p); mod = p * p - 1;
	Node a(5, 2); a = ksm(a, ksm(2, x) + 1); return (a.a * 2 + p - 1) % p;
}
int main(){read(t); for(Rint i = 1;i <= t;++ i) printf("Case #%d: %d\n", i, solve());}

HDU6340 Delightful Formula

题目描述:给定正整数 \(n\) 的质因子分解 \(n=\prod_{i=1}^mp_i^{\alpha_i}\) 和正整数 \(k\),求 \((\sum_{i=1}^n[i\bot n]\sum_{j=1}^ij^k)\bmod 998244353\)\(T\) 组数据。

数据范围:\(m\le 20,p_i,\alpha_i\le 10^9,k\le 10^5,\sum k\le 10^6\)

\[\begin{aligned} \mathbf{OGF}\{B\}&=\frac{x}{e^x-1}+x \\ \sum_{i=1}^ni^k&=k!\sum_{j=1}^{k+1}B_{k+1-j}\frac{n^j}{j!}\\ F(n,k)&=\sum_{d|n}d^k\mu(d)=\prod_{p|n}(1-p^k)\\ Ans&=\sum_{d|n}\mu(d)\sum_{i=1}^{\frac{n}{d}}\sum_{j=1}^{id}j^k \\ &=k!\sum_{d|n}\mu(d)\sum_{i=1}^{\frac{n}{d}}\sum_{j=1}^{k+1}B_{k+1-j}\frac{(id)^j}{j!} \\ &=k!\sum_{d|n}\mu(d)\sum_{j=1}^{k+1}B_{k+1-j}\frac{d^j}{j!}\sum_{i=1}^{\frac{n}{d}}i^j \\ &=k!\sum_{d|n}\mu(d)\sum_{j=1}^{k+1}B_{k+1-j}d^j\sum_{l=1}^{j+1}B_{j+1-l}\frac{n^l}{d^ll!} \\ &=k!\sum_{l=1}^{k+2}\frac{n^l}{l!}\sum_{j=[l=1]}^{k+2-l}B_{k+2-j-l}B_jF(n,j-1) \end{aligned} \]

\(F(n,k)\) 可以对每个 \(k\) 都计算一遍,然后就是卷积的形式。注意最后一个柿子中的变量取值范围。时间复杂度 \(O(k\log k+m\sum k)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int mod = 998244353, N = 1 << 18;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int t, m, n, k, a[N], b[N], rev[N], lim, w[2][N], fac[N], inv[N], pri[20], alp[20], res;
void init(int m){
	fac[0] = 1;
	for(Rint i = 1;i <= m;++ i) fac[i] = (LL) fac[i-1] * i % mod;
	inv[m] = ksm(fac[m], mod - 2);
	for(Rint i = m;i;-- i) inv[i-1] = (LL) inv[i] * i % mod;
}
void calrev(int len){
	int L = -1; lim = 1;
	while(lim <= len){lim <<= 1; ++ L;}
	for(Rint i = 0;i < lim;++ i) rev[i] = (rev[i>>1]>>1) | ((i&1)<<L);
	for(Rint mid = 1;mid < lim;mid <<= 1){
		w[0][mid] = w[1][mid] = 1; int Wn = ksm(3, (mod - 1) / (mid << 1));
		for(Rint i = 1;i < mid;++ i) w[0][mid + i] = (LL) Wn * w[0][mid + i - 1] % mod;
		for(Rint i = 1;i < mid;++ i) w[1][mid + i] = mod - w[0][(mid<<1) - i];
	}
}
void NTT(int *A, int op){
	for(Rint i = 0;i < lim;++ i) if(i < rev[i]) swap(A[i], A[rev[i]]);
	for(Rint mid = 1;mid < lim;mid <<= 1)
		for(Rint i = 0;i < lim;i += mid << 1)
			for(Rint j = 0;j < mid;++ j){
				int y = (LL) A[mid + i + j] * w[op][mid + j] % mod;
				qmo(A[mid + i + j] = A[i + j] - y); qmo(A[i + j] += y - mod);
			}
	if(op){
		int inv = ksm(lim, mod - 2);
		for(Rint i = 0;i < lim;++ i) A[i] = (LL) A[i] * inv % mod; 
	}
}
int ans[N], tmp[N];
void polyinv(int *A, int deg){
	if(deg == 1){ans[0] = ksm(A[0], mod - 2); return;}
	polyinv(A, deg + 1 >> 1); calrev(deg << 1);
	for(Rint i = 0;i < deg;++ i) tmp[i] = A[i];
	for(Rint i = deg;i < lim;++ i) tmp[i] = 0;
	NTT(tmp, 0); NTT(ans, 0);
	for(Rint i = 0;i < lim;++ i) ans[i] = (2ll - (LL) ans[i] * tmp[i] % mod + mod) * ans[i] % mod;
	NTT(ans, 1);
	for(Rint i = deg;i < lim;++ i) ans[i] = 0;
}
int calc(int d){
	int ans = 1; if(d < 0) d += mod - 1;
	for(Rint i = 0;i < m;++ i)
		ans = ans * (mod + 1ll - ksm(pri[i], d)) % mod;
	return ans;
}
void solve(){
	read(k); read(m); n = 1;
	for(Rint i = 0;i < m;++ i){read(pri[i]); read(alp[i]); pri[i] %= mod; n = (LL) n * ksm(pri[i], alp[i]) % mod;}
	calrev(k + 1 << 1); int tmp = calc(-1); a[0] = tmp; b[0] = 1;
	for(Rint i = 1;i <= k + 1;++ i) a[i] = (LL) ans[i] * calc(i - 1) % mod, b[i] = ans[i];
	for(Rint i = k + 2;i < lim;++ i) a[i] = b[i] = 0;
	NTT(a, 0); NTT(b, 0);
	for(Rint i = 0;i < lim;++ i) a[i] = (LL) a[i] * b[i] % mod;
	NTT(a, 1); res = 0;
	for(Rint i = 1, t = n;i <= k + 2;++ i, t = (LL) t * n % mod)
		qmo(res += (LL) t * inv[i] % mod * a[k + 2 - i] % mod - mod);
	qmo(res -= (LL) n * tmp % mod * ans[k + 1] % mod);
	printf("%d\n", (LL) res * fac[k] % mod);
}
int main(){
	read(t); init(100005);
	for(Rint i = 0;i <= 100000;++ i) a[i] = inv[i+1];
	polyinv(a, 100001); ++ ans[1];
	while(t --) solve();
}

CF232D Fence

题目描述:给定长为 \(n\) 的序列 \(h_i\)\(q\) 次询问 \(l_1,r_1\),求有多少个 \(l_2,r_2\),使得 \([l_1,r_1]\)\([l_2,r_2]\) 没有交集,且对应位置为相反数。

数据范围:\(n,q\le 10^5,|h_i|\le 10^9\).

CodeChef BOUNCE

题目描述:设 \(f(R,C)\) 表示,有一个被四周镜子围起来的 \(R\times C\) 的矩形,有一束光线从左下角的角平分线射出,光线按顺序碰到上、下、左、右边界,每次分别记录 U,D,L,R,直到碰到一角,得到的字符串。给定字符串 \(S\) 和正整数 \(n\),计算有多少个 \(1\le R,C\le n\),满足 \(S\)\(f(R,C)\) 的前缀。\(T\) 组数据。

数据范围:\(T\le 5,\sum|S|\le 10^6,4\le n\le 10^{10}\)

CodeChef DIVISORS

题目描述:给定正整数 \(b,x\),计算有多少个正整数 \(n\) 满足 \(nx\) 有一个因数 \(d\) 满足 \(n<d\le b\)\(T\) 组数据。

数据范围:\(T\le 40,b\le 10^{12},x\le 60\)

\(d_\min(n,x)\) 表示 \(nx\)\(>n\) 的最小因数。考虑对于每个 \(k\in[1,x)\),计算 \(k|nx\) 并且 \(\forall k<j<x,j\not|nx\)\(n\) 的个数 \(N(k,x)\)。因为 \(j|nx\Leftrightarrow j_x=\frac{j}{\gcd(j,x)}|n\),设 \(n=k_xm\),所以 \(\forall k<j<x,j\not|k_xmx\Leftrightarrow \frac{j}{\gcd(j,k_xx)}\not|m\)

因为 \(d_\min(n,x)\le b\),所以 \(m\le \frac{bk}{k_xx}\),即 \(m\le \lfloor\frac{b}{x/\gcd(x,k)}\rfloor\)。设 \(c=\lfloor\frac{b}{x/\gcd(x,k)}\rfloor\)\(N(k,x)\)\(m\le c\) 且不被 \(\frac{j}{\gcd(j,k_xx)}(k<j<x)\) 中的任意一个整除,这个可以使用容斥原理计算,算出 \(\frac{j}{\gcd(j,k_xx)}\)

CF720F Array Covering

题目描述:给定长为 \(n\) 的整数序列 \(a_i\) 和正整数 \(k\),找到恰好 \(k\) 个不相同的并为 \([1,n]\) 的区间,使得所有区间包含的元素之和之和最大。

数据范围:\(n\le 10^5,k\le \frac{n(n+1)}{2},|a_i|\le 5\times 10^4\)

显然,最大和的 \(k-n\) 个区间必须取。

CF gym 101745 E Increasing Sequence

题目描述:给定长为 \(n\) 的自然数序列 \(x_i\),你需要找到正整数 \(k\),将一部分 \(x_i\) 变为 \(k-x_i\),使得 \(x_i\) 递增,且 \(x_1\ge 0\)。需判断无解。

数据范围:\(n\le 10^5,x_i\le 10^9\)

我们可以考虑相邻两个元素 \(x'_i<x'_{i+1}\)\(4\) 种操作方案对应着一个 \(k\) 的值域范围,从 \(i\)\(i+1\) 的转移可以用 \(2\times 2\)\(01\) 矩阵表示,将 \(k\) 从小到大扫描线,用线段树动态维护所有矩阵的乘积,就可以找到合法的 \(k\),构造方案直接贪心或根据矩阵的前/后缀积即可。

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 100003, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, x[N], m; bool ans[N];
struct Mat {
	bool a[2][2];
	Mat(){memset(a, 0, sizeof a);}
	Mat operator * (const Mat &o) const {
		Mat res;
		for(Rint i = 0;i < 2;++ i)
			for(Rint k = 0;k < 2;++ k)
				for(Rint j = 0;j < 2;++ j) res.a[i][j] |= a[i][k] & o.a[k][j];
		return res;
	}
} t[N], seg[N<<2], pre[N];
struct Node {
	int val, id, x, y; bool w;
	Node(int v = 0, int id = 0, int x = 0, int y = 0, bool w = false): val(v), id(id), x(x), y(y), w(w){}
	bool operator < (const Node &o) const {return val < o.val;}
} q[N<<2];
void upd(int x, int L, int R, int p){
	if(L == R){seg[x] = t[p]; return;}
	int mid = L + R >> 1;
	if(p <= mid) upd(x<<1, L, mid, p);
	else upd(x<<1|1, mid+1, R, p);
	seg[x] = seg[x<<1] * seg[x<<1|1];
}
int main(){
	read(n);
	for(Rint i = 1;i <= n;++ i) read(x[i]);
	q[++ m] = Node(0, 1, 0, 0, 1); q[++ m] = Node(x[1], 1, 0, 1, 1);
	for(Rint i = 2;i <= n;++ i){
		if(x[i-1] < x[i]) q[++ m] = Node(0, i, 0, 0, 1);
		else if(x[i-1] > x[i]) q[++ m] = Node(x[i-1], i, 1, 1, 1);
		q[++ m] = Node(x[i] + x[i-1] + 1, i, 0, 1, 1);
		if(x[i]){q[++ m] = Node(x[i-1], i, 1, 0, 1); q[++ m] = Node(x[i-1] + x[i], i, 1, 0, 0);}
	}
	sort(q + 1, q + m + 1);
	for(Rint i = 1;i <= m;++ i){
		t[q[i].id].a[q[i].x][q[i].y] = q[i].w;
		upd(1, 1, n, q[i].id);
		if((i == m || q[i].val < q[i+1].val) && (seg[1].a[0][0] || seg[1].a[0][1])){
			printf("%d\n", q[i].val);
			pre[0].a[0][0] = true;
			for(Rint j = 1;j <= n;++ j) pre[j] = pre[j-1] * t[j];
			ans[n] = pre[n].a[0][1];
			for(Rint j = n-1;j;-- j) ans[j] = pre[j].a[0][1] && t[j+1].a[1][ans[j+1]];
			for(Rint j = 1;j <= n;++ j) printf("%d ", ans[j] ? q[i].val-x[j] : x[j]); return 0;
		}
	}
	puts("-1");
}

CF650E Clockwork Bomb

题目描述:给定两棵 \(n\) 个节点的树,每次操作将一条边断掉,连一条新边,并保证仍然是树,求将一棵树变为另一棵树的最小操作次数,并构造方案。

数据范围:\(n\le 5\times 10^5\)

容易得出答案是不相同的边的个数。因为每次操作必定可以让不相同的边的个数 -1.

CF641F

题目描述:给定两个 2-sat,求使其中一组不成立,另一组成立的变量取值。需判断无解。

数据范围:\(n\le 10^3,m_1,m_2\le n^2\)

先建出 2-sat 的图,然后做一遍传递闭包,大力讨论:

  1. 两个都无解,则无解。
  2. 一个有解一个无解,随便解一个。
  3. 两个都有解
    1. 存在一个点,一个 2-sat 要求必须为真,另一个可以为真,也可以为假,随便解一个。
    2. 存在其中一个 2-sat 的一条边,另一个 2-sat 可以不满足这条边的限制,也做完了。
#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 2003, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, m0, m1;
struct Graph {
	bitset<N> f[N]; bool ans[N], flag;
	void Set(int u){
		ans[u] = true;
		for(Rint i = 0;i < (n<<1);++ i) if(f[u][i] && !ans[i]) Set(i);
	}
	void build(int m){
		for(Rint i = 0, u, v;i < m;++ i){
			read(u); read(v);
			u = (abs(u)-1 << 1) | (u > 0);
			v = (abs(v)-1 << 1) | (v > 0);
			f[u].set(v^1); f[v].set(u^1);
		}
		for(Rint i = 0;i < (n<<1);++ i) f[i].set(i);
		for(Rint i = 0;i < (n<<1);++ i)
			for(Rint j = 0;j < (n<<1);++ j) if(f[j][i]) f[j] |= f[i];
		for(Rint i = 0;i < (n<<1);i += 2) if(f[i][i+1] && f[i+1][i]){flag = true; break;}
		for(Rint i = 0;i < (n<<1);i += 2)
			if(!(ans[i] || ans[i+1])) if(f[i][i+1]) Set(i+1); else if(f[i+1][i]) Set(i);
	}
	void solve(int x = -1, int y = -1){
		if(~x) Set(x); if(~y) Set(y);
		for(Rint i = 0;i < (n<<1);i += 2)
			if(!(ans[i] || ans[i+1])) Set(i);
		for(Rint i = 0;i < (n<<1);i += 2) printf("%d ", ans[i]);
	}
} g[2];
int main(){
	read(n); read(m0); read(m1);
	g[0].build(m0); g[1].build(m1);
	if(g[0].flag && g[1].flag){puts("SIMILAR"); return 0;}
	if(g[0].flag){g[1].solve(); return 0;}
	if(g[1].flag){g[0].solve(); return 0;}
	for(Rint i = 0;i < (n<<1);++ i)
		if(!g[0].ans[i] && g[1].ans[i]){g[0].solve(i^1); return 0;}
		else if(g[0].ans[i] && !g[1].ans[i]){g[1].solve(i^1); return 0;}
	for(Rint i = 0;i < (n<<1);++ i){
		if(!(g[0].ans[i] || g[0].ans[i^1]))
			for(Rint j = 0;j < i;++ j)
				if(!(g[0].ans[j] || g[0].ans[j^1]) && !g[0].f[i][j] && g[1].f[i][j]){
					g[0].solve(i, j^1); return 0;
				}
		if(!(g[1].ans[i] || g[1].ans[i^1]))
			for(Rint j = 0;j < i;++ j)
				if(!(g[1].ans[j] || g[1].ans[j^1]) && !g[1].f[i][j] && g[0].f[i][j]){
					g[1].solve(i, j^1); return 0;
				}
	} puts("SIMILAR");
}

AGC046D Secret Passage

题目描述:给定字符串 \(S\),每次操作你可以删掉 \(S\) 的前两个字符,将其中一个插入任意位置,求能得到的字符串数量\(\bmod 998244353\)

数据范围:\(|S|\le 300,|\Sigma|=2\)

LOJ3272「JOISC 2020 Day1」汉堡肉

题目描述:给定 \(n\) 个边与坐标轴平行的矩形 \((L_i,D_i,R_i,U_i)\) 和正整数 \(k\),要求找出 \(k\) 个点 \((x_i,y_i)\),使得每个矩形至少包含一个点。

数据范围:\(n\le 2\times 10^5,k\le 4,L_i,D_i,R_i,U_i\in[-10^9,10^9]\cap\Z\)。保证有解。

AGC044E Random Pawn

题目描述:给定 \(n\) 个排成环的自然数二元组 \((a_i,b_i)\),一开始你会等概率随机选一个位置开始,每次操作,若你当前在 \(p\) 位置,你可以选择结束操作并获得 \(a_i\) 分,或者失去 \(b_i\) 分并等概率随机选相邻两个位置中的一个再开始一轮操作。求期望获得分数的最大值。

数据范围:\(n\le 2\times 10^5,0\le a_i\le 10^{12},0\le b_i\le 100\)

发现到达最大值时一定会直接停止,所以断环成链。 \(f_p=\max(a_p,\frac{f_{p-1}+f_{p+1}}{2}-b_p)\)

然后 \(b_i=0\) 时就是 Balance Beam 这道题,考虑转化,构造 \(c_p\) 使 \(f_p+c_p=\max(a_p+c_p,\frac{(f_{p-1}+c_{p-1})+(f_{p+1}+c_{p+1})-c_{p-1}-c_{p+1}}{2}+c_p-b_p)\),要求常数项为 \(0\),即 \(c_{p+1}=2c_p-2b_p-c_{p-1}\),取 \(c_0=c_1=0\) 即可,时间复杂度 \(O(n)\)

AGC045F Division into Multiples

题目描述:给定 \(x\)\(a\)\(y\)\(b\),将它们分成若干组,求和为 \(c\) 的倍数的组的数量的最大值。\(T\) 组数据。

数据范围:\(T\le 2\times 10^4,1\le a,x,b,y,c\le 10^9\)

AGC045E Fragile Balls

题目描述:给定 \(n\) 个盒子和 \(m\) 个球,第 \(i\) 个球在第 \(a_i\) 个盒子里。每次你可以选择一个包含至少两个球的盒子,拿出一个球到另一个盒子里。第 \(i\) 个球的移动次数不能超过 \(c_i\),求最少的移动次数使第 \(i\) 个球到第 \(b_i\) 个盒子里。需判断无解。

数据范围:\(n,m\le 10^5\)

URAL2057 Non-palindromic cutting

题目描述:给定字符串 \(S\),你需要将其分割成非回文子串,求段数的最小值和最大值。

数据范围:\(|S|\le2\times10^5\)

CF932G Palindrome Partition

题目描述:给定字符串 \(S\),求将其划分回文的方案数。

数据范围:\(|S|\le10^6\)

Data Structure Quiz

题目描述:给定 \(n\times n\) 的矩阵,要求支持 \(m_1\) 次矩形加,再求 \(m_2\) 次矩形最大值。

数据范围:\(n,m_1\le 5\times 10^4,m_2\le 5\times 10^5\)

CodeChef SUMDIS

题目描述:给定 \(n\) 个点的 DAG,其中的边仅有:

  • \((i,i+1,a_i)\),其中 \(i\in[1,n-1]\)
  • \((i,i+2,b_i)\),其中 \(i\in[1,n-2]\)
  • \((i,i+3,c_i)\),其中 \(i\in[1,n-3]\)

求两两最短路之和。\(T\) 组数据。

数据范围:\(T\le 10^4,n\le 10^5,\sum n\le 3\times 10^5,a_i,b_i,c_i\le 10^4\)

考虑分治,跨过中间三个点的路径必须经过这三个点之一。枚举经过哪一个最短,转化为二维数点问题,排序+树状数组即可,时间复杂度 \(O(n\log n)\)

TCO SemiPerfectPower

题目描述:定义半完全幂数为形如 \(a\times b^c\) 的数,其中 \(1\le a<b,1<c\)。求 \([L,R]\) 的半完全幂数的个数。

数据范围:\(1\le L\le R\le 8\times 10^{16}\)

发现当 \(c>3\) 时,可以转化为 \(c=2,3\) 的情况,所以只考虑 \(c=2,3\)。差分转化为求 \([1,n]\) 的个数。

若可以表示为 \(c=2\) 的情况,由于 \(a\le \sqrt[3]{n}\),直接枚举 \(a\) 即可。

若可以表示为 \(c=3\) 而不能表示为 \(c=2\)

AGC044D Guess the Password

这是一道交互题

题目描述:交互器有一个长度不超过 \(L\) 的字符串 \(S\),你可以询问 \(Q\) 次长度不超过 \(L\) 的字符串 \(T\),交互器会回答 \(S\)\(T\) 的编辑距离(操作定义为插入,删除,替换),你需要求出 \(S\)

数据范围:\(L=128,Q=850,|\Sigma|=62\),数据在交互开始前确定。

有一个我不知道的结论,两个字符串 \(A,B\) 的编辑距离等于 \(\max(|A|,|B|)\) 减去最长公共子序列的长度。证明考虑两者的 dp 转移。

所以我们可以改为查询出最长公共子序列的长度。查询 \(L\)\(c\) 的字符串可以得到字符 \(c\) 的出现次数。还可以查询字符串 \(T\) 是否为 \(S\) 的子序列,可以考虑归并排序的方法,将两个字符串按 \(S\) 中的顺序合并,最多需要 \(|A|+|B|-1\) 次操作合并两个字符串 \(A,B\),最后得到的字符串。使用启发式合并,操作次数就是 \(O(L\log L+|\Sigma|)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int mod = 998244353, L = 128;
const char id[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n;
int query(const string &s){cout << "? " << s << endl; int x; read(x); return x;}
bool check(const string &s){return query(s) + s.size() == n;}
struct cmp {bool operator () (const string &a, const string &b){return a.size() > b.size();}};
priority_queue<string, vector<string>, cmp> pq;
int main(){
	for(Rint i = 0;i < 62;++ i){
		int tmp = L - query(string(L, id[i]));
		n += tmp; if(tmp) pq.push(string(tmp, id[i]));
	}
	while(pq.size() > 1){
		string a = pq.top(); pq.pop();
		string b = pq.top(), res; pq.pop();
		int p1 = 0, p2 = 0, l1 = a.size(), l2 = b.size();
		while(p1 < l1 && p2 < l2)
			if(check(res + a[p1] + b.substr(p2))) res += a[p1 ++];
			else res += b[p2 ++];
		if(p1 < l1) res += a.substr(p1);
		if(p2 < l2) res += b.substr(p2);
		pq.push(res);
	}
	cout << "! " << pq.top() << endl;
}

2019-2020 XX Opencup GP of Tokyo E Count Modulo 2

题目描述:给定自然数集合 \(A\) 和正整数 \(n,S\),求满足

  • \(\sum_{i=1}^na_i=S\)
  • \(\forall i\in[1,n],a_i\in A\)

的长为 \(n\) 的序列 \(a\) 的个数\(\bmod 2\)\(T\) 组数据。

数据范围:\(T\le 5,|A|\le 200,n,S\le 10^{18}\)\(A\) 中的元素不超过 \(v=10^5\)

\(A_i\) 取了 \(b_i\) 次,则当 \(\sum_{i=1}^nA_ib_i=S,\sum b_i=n\) 时,贡献答案 \(\frac{n!}{\prod b_i!}\) 次。根据 Kummer Theorem,当且仅当 \(b_i\) 两两按位与为 \(0\) 的时候,贡献答案 \(1\) 次。于是问题转化为,对于 \(n\) 的所有二进制位 \(i\),可以将 \(S\) 减去 \(2^ia_j\),求最后得到 \(0\) 的方案数。我们从高到低考虑 \(n\) 的二进制位 \(i\),用 dp 枚举减去哪一项,由于当 \(S>2^{i+1}v\) 时一定不合法,所以可以考虑每次滚动二进制位,将较大的一半扔掉。使用 bitset 优化转移,时间复杂度 \(O(\frac{v|A|\log n}{w})\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int t, k, now, pre, a[203]; LL n, s;
bitset<200005> f[2], tmp;
int main(){
	read(t);
	while(t --){
		read(n); read(s); read(k); now = 0; pre = 1;
		for(Rint i = 0;i < k;++ i) read(a[i]);
		f[0].reset(); f[0].set(0);
		for(Rint i = 60;~i;-- i){
			swap(now, pre); tmp.reset(); f[now].reset();
			if((n >> i) & 1) for(Rint j = 0;j < k;++ j) tmp ^= f[pre] >> a[j];
			else tmp = f[pre];
			if(i) for(Rint j = 0;j <= 100000;++ j) if(tmp[j]) f[now].flip(j << 1 | ((s >> i-1) & 1));
		}
		cout << tmp[0] << endl;
	}
}

Luogu4464 [国家集训队]JZPKIL

题目描述:给定自然数 \(n,x,y\),求

\[\sum_{i=1}^n\gcd(i,n)^x\text{lcm}(i,n)^y\bmod(10^9+7) \]

\(T\) 组数据。

数据范围:\(T\le 100,n\le 10^{18},0\le x,y\le 3000\)

\[\begin{aligned} Ans&=n^y\sum_{i=1}^n\gcd(i,n)^{x-y}i^y \\ &=n^y\sum_{d|n}d^x\sum_{i=1}^{\frac{n}{d}}[\gcd(i,\frac{n}{d})=1]i^y \\ &=n^y\sum_{d|n}d^x\sum_{i=1}^{\frac{n}{d}}i^y\sum_{e|\frac{n}{d},e|i}\mu(e) \\ &=n^y\sum_{d|n}d^x\sum_{e|\frac{n}{d}}e^y\mu(e)\sum_{i=1}^{\frac{n}{de}}i^y \\ &=n^yy!\sum_{d|n}d^x\sum_{e|\frac{n}{d}}e^y\mu(e)\sum_{i=1}^{y+1}\frac{B_{y+1-i}}{i!}(\frac{n}{de})^i \\ &=n^yy!\sum_{i=1}^{y+1}\frac{B_{y+1-i}}{i!}\sum_{d|n}d^x\sum_{e|\frac{n}{d}}e^y\mu(e)(\frac{n}{de})^i \end{aligned} \]

设后面的和式为 \(F_i(n)\),它可以看成三个积性函数的 Dirichlet 卷积,所以 \(F_i(n)\) 是积性函数。

\(f=\text{id}^x\times\text{id}^c\)\(F_c(1)=1\)\(F_c(p)=p^x+p^c-p^y\)\(F_c(p^k)=f(p^k)-f(p^{k-1})p^y\)

其中 \(f(p^k)=p^{kc}\sum_{i=0}^kp^{i(x-c)}\)。使用 Pollard-rho 分解质因数,直接计算。求伯努利数的时候可以暴力。

\[B_k=1-\sum_{i=0}^{k-1}\frac{B_i}{(k+1-i)!},B_0=1 \]

狄利克雷生成函数

对于一个数论函数 \(f_i\),定义它的 DGF 为 \(F(x)=\sum\limits_{i=1}^{+\infty}\frac{f_i}{i^x}\)。和 EGF 一样,简单的 DGF 可以用黎曼 Zeta 函数 \(\zeta(s)\) 表示。

易得 DGF 乘积对应 Dirichlet 卷积。这个东西可以证明 Mobius 反演帮助我们在面对比较复杂的 Dirichlet 卷积时快速计算。

LOJ6713「EC Final 2019」狄利克雷 k 次根 加强版

题目描述:给定 \(f_1,f_2,\dots,f_n\),求 \(g_1,g_2,\dots,g_n\) 使 Dirichlet 卷积,模质数 \(p\) 意义下 \(g^k=f\)

数据范围:\(n\le 10^6,p=998244353\)

考虑 exp/ln 方法,我们知道 \(F'(x)=\sum(\frac{f_i}{i^x})'=\sum\frac{f_i\ln i}{i^x}\)\(\ln F(x)=\int\frac{F'(x)}{F(x)}\),求逆直接计算即可。\(\exp\) 可以直接取逆运算。时间复杂度是枚举倍数的 \(O(n\log n)\)

但有个问题,\(\ln i\)\(\mathbb{F}_p\) 下没有定义,但其实之后在取积分的时候会去掉 \(\ln\),实际上只用到了 \(\ln\) 的完全积性和 \(\ln 1=0\),于是随便找一个替代,比如质因子质数之和。

当然也可以用 \(f^p=\epsilon\) 得到 \(g=f^{k^{-1}\bmod p}\),使用幂函数的递推公式可以做到同样的复杂度。

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 1000003, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, k, a[N], b[N], pri[N], tot, c[N], inv[100];
bool notp[N];
int main(){
	read(n); read(k); notp[0] = notp[1] = true; k = ksm(k, mod - 2);
	for(Rint i = 2;i <= n;++ i){
		if(!notp[i]) pri[tot ++] = i, c[i] = 1;
		for(Rint j = 0;j < tot && i * pri[j] <= n;++ j){
			notp[i * pri[j]] = true;
			c[i * pri[j]] = c[i] + 1;
			if(!(i % pri[j])) break;
		}
	}
	inv[0] = inv[1] = 1;
	for(Rint i = 2;i < 100;++ i) inv[i] = mod - (LL) mod / i * inv[mod % i] % mod;
	for(Rint i = 1;i <= n;++ i) read(a[i]);
	for(Rint i = 1;i <= n;++ i) b[i] = (LL) a[i] * c[i] % mod;
	for(Rint i = 1;i <= n;++ i){
		for(Rint j = 2;i * j <= n;++ j)
			qmo(b[i*j] -= (LL) b[i] * a[j] % mod);
		b[i] = (LL) b[i] * inv[c[i]] % mod;
	}
	for(Rint i = 1;i <= n;++ i){b[i] = (LL) b[i] * k % mod * c[i] % mod; a[i] = 0;} a[1] = 1;
	for(Rint i = 1;i <= n;++ i){
		a[i] = (LL) a[i] * inv[c[i]] % mod;
		for(Rint j = 2;i * j <= n;++ j)
			qmo(a[i*j] += (LL) a[i] * b[j] % mod - mod);
	}
	for(Rint i = 1;i <= n;++ i) printf("%d ", a[i]);
}

AGC041F Histogram Rooks

题目描述:给定宽为 \(n\) 的 Histogram,第 \(i\) 列有 \(h_i\) 个方格,下边界对齐。求在格子里放一些使得所有格子都被至少一个的攻击范围覆盖到的方案数\(\bmod 998244353\),这里的不能越过空位。

数据范围:\(1\le h_i\le n\le 400\)

注意到不能越过空位,于是想到建小根笛卡尔树,每个节点的儿子不互相影响,符合树形 dp 的性质。

注意到所有格子都在攻击范围的条件,于是想到容斥变为钦定 \(s\) 个格子不能被覆盖,容斥系数为 \((-1)^s\)

若一个格子没有被覆盖,则它所在的列都没有,于是可以设 \(dp_{s,i}\) 表示 \(s\) 结点所在区间中,有 \(i\) 列包含钦定的不被覆盖的格子的方案数(带容斥系数)。关于处理每一列都包含钦定的格子,考虑再次容斥,钦定 \(j\) 列不包含被钦定的格子。那么长为 \(len\) 的一整行的贡献就是:

  1. 没有任何一个方格钦定没有被覆盖,则除钦定的不被覆盖的列之外,可以随便放,方案数为 \(2^{len-i}\)

  2. 有些方格钦定被覆盖,则在钦定的不被覆盖的列中,钦定不包含被钦定的格子的列之外的 \(i-j\) 个位置,可以选择一些放,容斥系数为 -1 的的个数次方,即 \(\sum_{k=1}^{i-j}(-1)^k\binom{i-j}{k}=-[i>j]\)

所以当前节点中一行的贡献就是 \(2^{len-i}-[i>j]\)。又因为 \(i\ge j\),所以我们不关心 \(j\) 的大小,只用多记一维表示 \([i=j]\) 即可。时空复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 403, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, h[N], pw2[N], ans, f[N][N], g[N][N];
int solve(int l, int r, int val){
	if(l > r) return 0;
	int mid = l;
	for(Rint i = l + 1;i <= r;++ i) if(h[mid] > h[i]) mid = i;
	int lc = solve(l, mid-1, h[mid]), rc = solve(mid+1, r, h[mid]);
	for(Rint i = 0;i <= mid-l;++ i)
		for(Rint j = 0;j <= r-mid;++ j){
			int tmp = (LL) f[lc][i] * f[rc][j] % mod;
			qmo(f[mid][i+j] += tmp - mod); qmo(f[mid][i+j+1] -= tmp); qmo(g[mid][i+j+1] += tmp - mod);
			qmo(g[mid][i+j] += (LL) (f[lc][i] + g[lc][i]) * (f[rc][j] + g[rc][j]) % mod - mod); qmo(g[mid][i+j] -= tmp);
		}
	for(Rint i = 0;i <= r-l+1;++ i){
		f[mid][i] = (LL) f[mid][i] * ksm(pw2[r-l+1-i], h[mid] - val) % mod;
		g[mid][i] = (LL) g[mid][i] * ksm(pw2[r-l+1-i]-1, h[mid] - val) % mod;
	} return mid;
}
int main(){
	read(n); pw2[0] = f[0][0] = 1;
	for(Rint i = 1;i <= n;++ i){read(h[i]); qmo(pw2[i] = (pw2[i-1]<<1) - mod);}
	int rt = solve(1, n, 0);
	for(Rint i = 0;i <= n;++ i){qmo(ans += f[rt][i] - mod); qmo(ans += g[rt][i] - mod);}
	printf("%d\n", ans);
}

UOJ504【JOISC2020】变色龙

这是一道交互题

题目描述:有 \(2n\) 只变色龙,它们的原色有 \(n\) 种,性别有 X,Y 两种,每种颜色恰好有一个 X 性变色龙和一个 Y 性变色龙的原色是这种颜色。每一只变色龙都 love 一只异性变色龙,且每只变色龙与它 love 的变色龙原色不同,且没有两个变色龙喜欢同一个变色龙。你可以组织至多 \(Q\) 次见面会,对于每只参加见面会的变色龙,若它 love 的变色龙也参加了见面会,则它的颜色是它喜欢的变色龙的颜色,否则是它的原色,交互库返回这场见面会中颜色的数量。你需要找出所有相同颜色的 \(n\) 对变色龙。

数据范围:\(n\le 500,Q=20000\)

【解题思路 TODO】

#include<bits/stdc++.h>
#include"chameleon.h"
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef vector<int> VI;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 1003, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
VI V[2], E[N];
int col[N], suki[N];
void dfs(int x, int c){
	col[x] = c; V[c].PB(x);
	for(Rint v : E[x]) if(col[v] == -1) dfs(v, !c);
}
void add(VI vec, int x){
	random_shuffle(vec.begin(), vec.end());
	while(true){
		vec.PB(x);
		if(Query(vec) == vec.size()) return;
		vec.pop_back();
		int l = 0, r = vec.size() - 1, mid, ans = -1;
		while(l <= r){
			mid = l + r >> 1;
			VI tmp(vec.begin() + l, vec.begin() + mid + 1); tmp.PB(x);
			if(Query(tmp) == tmp.size()) l = mid + 1;
			else ans = mid, r = mid - 1;
		}
		if(ans == -1) return;
		int y = vec[ans]; E[x].PB(y); E[y].PB(x);
		vec.erase(vec.begin(), vec.begin() + ans + 1);
	}
}
void work(int x){
	V[0].clear(); V[1].clear();
	for(Rint i = 1;i < x;++ i) col[i] = -1;
	for(Rint i = 1;i < x;++ i) if(col[i] == -1) dfs(i, 0);
	add(V[0], x); add(V[1], x);
}
void Solve(int n){
	srand(time(0));
	for(Rint i = 1;i <= (n<<1);++ i) work(i);
	for(Rint i = 1;i <= (n<<1);++ i) if(E[i].size() == 3)
		for(Rint j = 0;j < 3;++ j){
			VI tmp = {i};
			for(Rint k = 0;k < 3;++ k) if(j != k) tmp.PB(E[i][k]);
			if(Query(tmp) == 1){suki[i] = E[i][j]; break;}
		}
	for(Rint i = 1;i <= (n<<1);++ i){
		if(E[i].size() == 1){if(i < E[i][0]) Answer(i, E[i][0]); continue;}
		for(Rint j = 0;j < 3;++ j){
			if(suki[i] == E[i][j] || suki[E[i][j]] == i) continue;
			if(i < E[i][j]) Answer(i, E[i][j]);
		}
	}
}

UOJ507【JOISC2020】星座3

题目描述:给定一个宽为 \(n\) 的 Histogram \(h_i\)\(m\) 个格子是特殊的,第 \(i\) 个特殊格子有权值 \(c_i\),你需要一些特殊格子,使得任何一个子矩形都至多包括一个特殊格子。求这些特殊格子的权值和的最小值。

数据范围:\(n,m\le 2\times 10^5,1\le h_i\le n,1\le c_i\le 10^9\)

容易想到建出小根笛卡尔树,那么每个节点所在的区间中至多只能有一个特殊格子。于是自下往上 dp,考虑保留哪一个特殊格子。

若保留了一个特殊格子,则它到根的路径上的区间都不能保留特殊格子。于是若保留当前区间的特殊格子,就要将子树内的所有特殊格子扔掉,比较两者权值来决策即可。维护子树和直接用 BIT,时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define PB push_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 200003;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
int n, m; LL ans, tr[N];
vector<int> h[N];
vector<pii> v[N];
struct UFS {
    int fa[N];
    int get(int x){return x == fa[x] ? x : fa[x] = get(fa[x]);}
} L, R;
int EI(int x){return x & -x;}
void upd(int p, LL v){for(;p <= n;p += EI(p)) tr[p] += v;}
LL qry(int p){LL t = 0; for(;p;p -= EI(p)) t += tr[p]; return t;}
int main(){
    read(n);
    for(Rint i = 1;i <= n+1;++ i) L.fa[i] = R.fa[i] = i;
    for(Rint i = 1, x;i <= n;++ i){
        read(x); h[x].PB(i);
    } read(m);
    while(m --){
        int x, y, c;
        read(x); read(y); read(c);
        v[y].PB(MP(x, c));
    }
    for(Rint i = 1;i <= n;++ i){
        LL tmp;
        for(pii b : v[i])
            if((tmp = qry(b.fi)) >= b.se) ans += b.se;
            else {
                ans += tmp;
                upd(L.get(b.fi) + 1, b.se - tmp);
                upd(R.get(b.fi), tmp - b.se);
            }
        for(Rint j : h[i]){L.fa[j] = j-1; R.fa[j] = j+1;}
    }
    printf("%lld\n", ans);
}

LOJ3081「2019 集训队互测 Day 5」简单计数

题目描述:给定正整数 \(n,k\) 和自然数集合 \(S\),你需要求 \(n\) 个点的带标号有向无环图数量\(\bmod 998244353\),使得每个点出度 \(\le 1\),入度 \(\in S\),边有 \(k\) 种颜色。

数据范围:\(n\le 9\times 10^8,k\le 10^7,S\subseteq [0,3]\)

LOJ3070「2019 集训队互测 Day 1」最短路径

题目描述:给定 \(n\) 个点的基环树(无向)求 \(\sum_{u<v}\text{dis}(u,v)^k\bmod 998244353\)

数据范围:\(n\le 10^5,k\le 10^9\)

看到 \(k\) 的范围这么大就不太知道如何转化,于是考虑直接对 \(i\in[0,n)\) 求出 \(\text{dis}(u,v)=i\) 的点对数。

然后每棵子树直接点分治+FFT,环上的贡献考虑距离,分类讨论之后同理。时间复杂度 \(O(n\log^2n)\)

-CF Gym 102129 B Associativity Degree

题目描述:给定正整数 \(n,q\),设 \(U=[1,n]\cap\Z\)\(q\) 次询问正整数 \(k\),你要构造函数 \(f:U^2\rightarrow U\) 使得有 \(k\)\(a,b,c\in U\) 满足 \(f(f(a,b),c)=f(a,f(b,c))\)。需判断无解。

数据范围:\(n\le 64,qn^2\le 10^6\)

CF Gym 102129 D Basis Change

题目描述:给定长为 \(k\) 的正整数序列 \(a,b\),考虑所有满足 \(\forall n>k,F_n=\sum_{i=1}^ka_iF_{n-i}\) 的无穷序列 \(F\),你需要求出长为 \(k\) 的序列 \(c\),使得 \(\forall n>b_k,F_n=\sum_{i=1}^kc_iF_{n-b_i}\)。对于 \(i\in[1,k]\cap\Z\),输出 \(c_i\bmod (10^9+7)\)

数据范围:\(k\le 128,a_i\le 10^9,b_{i-1}<b_i\le 10^9\)

根据 \(F\) 的生成函数知道,满足条件当且仅当 \(A(x)=(1-\sum_{i=1}^ka_ix^i)\)\((1-\sum_{i=1}^kc_ix^{b_i})\) 的因式,则可以将 \(x^{b_k}\bmod A(x)\) 计算出来,然后用高斯消元解出 \(c_i\),时间复杂度 \(O(k^3\log V)\)\(O(k^3+k^2\log k\log V)\)(FFT优化)。

CF Gym 102129 G Permutant

题目描述:对于 \(n\times n\) 的矩阵 \(A\),给定长为 \(n\) 的序列 \(a\) 长为 \(n\) 的排列 \(p\),满足 \(A_{0,i}=a_i\)\(A_{i,j}=A_{i-1,p_j}\)。求 \((\det A)\bmod(10^9+7)\)

数据范围:\(n\le 5000,a_i\le 10^9\)

有一个我不知道的结论,如果 \(p\) 的环数 \(\ge 2\),则答案为 \(0\)。证明可以考虑由于每个环的和一定,那么一定存在一些\(n\) 维向量不属于行向量组张成的线性空间,所以 \(\text{rank}(A)<n\)

剩下的情况,可以通过交换列将 \(A_{i,j}\) 变为 \(a_{(j-i)\bmod n}\)

关于循环矩阵,这是一个经典结论(?),构造矩阵 \(B_{i,j}=\omega_n^i\),构造多项式 \(f(x)=\sum_{i=0}^{n-1}a_ix^i\),则 \((A\times B)_{i,j}=\omega_n^{ij}f(\omega_n^j)\),则 \(|AB|=\prod_{i=0}^{n-1}f(\omega_n^{i})|B|\),所以 \(|A|=\prod_{i=0}^{n-1}f(\omega_n^i)\)

单位根存在时可以用 Chirp-Z Transform 直接多点求值,但是单位根有可能不存在。

有一个东西叫结式,也是经典东西。

定义两个多项式 \(A(x)=a_n\prod_{i=0}^{n-1}(x-\mu_i),B(x)=b_m\prod_{i=0}^{m-1}(x-\lambda_i)\) 的结式

\[\mathcal{R}(A,B)=a_n^mb_m^n\prod_{i=0}^{n-1}\prod_{j=0}^{m-1}(\mu_i-\lambda_j)=a_n^m\prod_{i=0}^{n-1}B(\mu_i)=(-1)^{nm}b_m^n\prod_{i=0}^{m-1}A(\lambda_i) \]

易得 \(\mathcal{R}(A,B)=(-1)^{nm}\mathcal{R}(B,A)\)。设 \(C=B\bmod A\),则 \(B(\mu_i)=C(\mu_i)\),所以 \(\mathcal{R}(A,B)=(-1)^{nm}a_n^{m-\text{deg}(C)}\mathcal{R}(C,A)\),使用这个递归式计算,时间复杂度为 \(O(nm)\)

\(g(x)=x^n-1\),则答案就是 \(\mathcal{R}(f,g)\)。直接计算,时间复杂度 \(O(n^2)\)

LOJ3280「JOISC 2020 Day4」首都城市

题目描述:给定 \(n\) 个点的树,将点集划分为 \(k\) 个集合,求能选择其中尽量少的多少个集合合并,得到一个连通块。

数据范围:\(k\le n\le 10^5\)

我们可以将每个集合的虚树建出来,如果集合 \(i\) 的虚树包含另一个集合 \(j\) 的节点,那么连一条边 \(i\rightarrow j\),答案就是出度为 \(0\) 的强连通分量的 \(siz\) 最小值。使用倍增优化连边,复杂度 \(O(n\log n)\)

还有一种更高妙的连边方法,即点 \(i\) 所在集合向它父亲所在集合连边当且仅当,\(i\) 不是它所在集合的虚树的根。容易证明两种建图方法做传递闭包之后相等。复杂度 \(O(n)\)

#include<bits/stdc++.h>
#define Rint register int
#define PB push_back
using namespace std;
const int N = 200003;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int n, k, head[N], to[N<<1], nxt[N<<1], col[N], dfn[N], out[N], fa[N], tim, rt[N];
vector<int> E[N];
void add(int a, int b){
    static int cnt = 0;
    to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt;
}
void dfs(int x){
    dfn[x] = ++ tim;
    for(Rint i = head[x];i;i = nxt[i])
        if(to[i] != fa[x]){fa[to[i]] = x; dfs(to[i]);}
    out[x] = tim;
}
int stk[N], low[N], top, ccol[N], cnum, ans; bool ins[N];
void tarjan(int x){
    stk[++ top] = x; dfn[x] = low[x] = ++ tim; ins[x] = true; int tmp = top;
    for(Rint v : E[x])
        if(!dfn[v]){tarjan(v); chmin(low[x], low[v]);}
        else if(ins[v]) chmin(low[x], dfn[v]);
    if(dfn[x] == low[x]){
        ++ cnum; bool flag = true;
        for(Rint i = tmp;i <= top;++ i){ins[stk[i]] = false; ccol[stk[i]] = cnum;}
        for(Rint i = tmp;i <= top && flag;++ i)
            for(Rint v : E[stk[i]]) flag &= ccol[v] == cnum;
        if(flag) chmin(ans, top - tmp); top = tmp - 1;
    }
}
int main(){
    read(n); read(k); ans = k-1;
    for(Rint i = 1, u, v;i < n;++ i){
        read(u); read(v); add(u, v); add(v, u);
    } dfs(1);
    for(Rint i = 1;i <= n;++ i){
        read(col[i]); int &x = rt[col[i]];
        if(!x || dfn[x] > dfn[i]) x = i;
    }
    for(Rint i = 1;i <= n;++ i) if(dfn[i] > out[rt[col[i]]]) rt[col[i]] = 0;
    for(Rint i = 1;i <= n;++ i) if(i != rt[col[i]]) E[col[i]].PB(col[fa[i]]);
    for(Rint i = 1;i <= k;++ i) dfn[i] = 0; tim = 0;
    for(Rint i = 1;i <= k;++ i) if(!dfn[i]) tarjan(i);
    printf("%d\n", ans);
}

LOJ2734「JOISC 2016 Day 2」女装大佬

题目描述:原题面

手玩一下就发现,不合法的情况就是某个时刻,队列中只有男生。将女生设为 \(-1\),男生设为 \(+1\),那么如果有后缀和 \(\ge 2\),那么说明这个位置的男生后面的女生都**了,于是不合法。

如果将最后一个男生从末尾调到开头,那么中间所有人的不满意度 \(+1\),且后缀和 \(-1\)。所以答案就是最大后缀和 \(-1\)。直接计算,时间复杂度与读入复杂度同阶。

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = 200003;
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
LL n, p[N], b[N], mp[N], tot, ans = 1, tmp; int m;
char str[N];
int main(){
    scanf("%lld%d", &n, &m);
    for(Rint i = 1;i <= m;++ i){
        scanf("%s%lld", str, b+i); int len = strlen(str);
        for(Rint j = len-1;~j;-- j){
            p[i] += str[j] == 'M' ? 1 : -1; chmax(mp[i], p[i]);
        } tot += p[i] * b[i];
    }
    if(tot > 0) return puts("-1"), 0;
    for(Rint i = m;i;-- i){
        chmax(ans, tmp + (b[i]-1) * max(0ll, p[i]) + mp[i]);
        tmp += b[i] * p[i];
    }
    printf("%lld\n", ans-1);
}

LOJ3030「JOISC 2019 Day1」考试

题目描述:给定 \(n\) 个点 \((x_i,y_i)\)\(q\) 次询问 \(a,b,c\),求有多少个点满足 \(x_i\ge a,y_i\ge b,x_i+y_i\ge c\)

数据范围:\(n,q\le 10^5,0\le x_i,y_i\le 10^9\)

三维数点模板?

考虑反面,可以划分为三个区域:\(x_i<a\and x_i+y_i\ge c\)\(y_i<b\and x_i+y_i\ge c\)\(x_i+y_i<c\)。直接计算,时间复杂度 \(O(n\log n)\)

LOJ2737「JOISC 2016 Day 3」电报

题目描述:给定长为 \(n\) 的正整数序列 \(a_i,c_i\),求 \(\sum_{i=1}^n[a_i\ne p_i]c_i\) 的最大值,其中 \(p\) 是长为 \(n\) 的排列,且若所有 \(i\)\(p_i\) 连有向边,得到一个环。

数据范围:\(n\le 10^5,1\le a_i\le n,a_i\ne i,1\le c_i\le 10^9\)

这里有一个小 trick,若 \(i\rightarrow a_i\),则每个连通块是基环内向树。

先特判一下给定的是一个大环,那么答案是 \(0\)。否则每个连通块都需要去掉一些边变成一条链。这个可以直接维护,时间复杂度 \(O(n)\)

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = 100003;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, a[N], vis[N]; LL ans, mv[2][N], c[N];
int main(){
    read(n);
    for(Rint i = 1;i <= n;++ i){read(a[i]); read(c[i]); ans += c[i];}
    for(Rint i = 1;i <= n;++ i) if(!vis[i]){
        int j = i;
        while(!vis[j]){vis[j] = i; j = a[j];}
        if(vis[j] == i){
            int siz = 0;
            while(~vis[j]){vis[j] = -1; j = a[j]; ++ siz;}
            if(n == siz){puts("0"); return 0;}
        }
    }
    for(Rint i = 1;i <= n;++ i){
        chmax(mv[0][a[i]], c[i]);
        if(~vis[i]) chmax(mv[1][a[i]], c[i]);
    }
    for(Rint i = 1;i <= n;++ i) ans -= mv[0][i];
    for(Rint i = 1;i <= n;++ i) if(vis[i] == -1){
        LL tmp = 1e18; int j = i;
        while(vis[j] == -1){
            chmin(tmp, mv[0][j] - mv[1][j]);
            vis[j] = 0; j = a[j];
        }
        ans += tmp;
    }
    printf("%lld\n", ans);
}

LOJ2391「JOISC 2017 Day 1」港口设施

题目描述:给定 \(n\) 个区间 \([a_i,b_i]\),求有多少种将它们划分为两个集合的方法,使得同一个集合里的区间相离或包含。答案对 \(10^9+7\) 取模。

数据范围:\(n\le 10^6,1\le a_i,b_i\le 2n\),这 \(2n\)\(a_i,b_i\) 两两不同。

若两个区间相交就连边,得到的是二分图则答案为 \(2^{连通块个数}\),否则为 \(0\)

连边需要优化一下。从左到右进行扫描线,按左端点排序对区间重标号,按右端点遍历区间,用并查集维护最左的与当前区间相交的区间。同时使用 \(nxt\) 数组维护与当前区间可以确定在二分图染色中同色的最右区间(显然这些区间的标号是相邻的),这些区间不用连边,就可以直接 skip,于是边数变为 \(O(n)\)。时间复杂度 \(O(n)\)

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = 1000003, mod = 1e9 + 7;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, id[N<<1], fa[N], nxt[N], vis[N], cnt, ans = 1, col[N];
vector<int> E[N];
int get(int x){return x == fa[x] ? x : fa[x] = get(fa[x]);}
void dfs(int x){
    for(Rint v : E[x])
        if(!col[v]){col[v] = 3 - col[x]; dfs(v);}
        else if(col[x] == col[v]){puts("0"); exit(0);}
}
int main(){
    read(n); fa[n+1] = nxt[n+1] = n+1;
    for(Rint i = 1, l, r;i <= n;++ i){
        read(l); read(r); id[l] = id[r] = fa[i] = nxt[i] = i;
    }
    for(Rint i = 1;i <= (n<<1);++ i)
        if(!vis[id[i]]) vis[id[i]] = ++cnt;
        else {
            int u = vis[id[i]]; fa[u] = get(u + 1);
            for(Rint v = fa[u], tmp;v <= cnt;v = tmp){
                E[u].push_back(v); E[v].push_back(u);
                tmp = get(nxt[v] + 1); nxt[v] = cnt;
            }
        }
    for(Rint i = 1;i <= n;++ i)
        if(!col[i]){col[i] = 1; dfs(i); ans *= 2; if(ans >= mod) ans -= mod;}
    printf("%d\n", ans);
}

LOJ3275「JOISC 2020 Day2」有趣的 Joitter 交友

题目描述:给定 \(n\) 个点的有向图,\(m\) 次询问,每次加入一条边 \(a\rightarrow b\),询问在 \(x\rightarrow y\rightarrow z\rightarrow y\and x\ne z\Rightarrow x\rightarrow z\) 的情况下图的边数。

数据范围:\(n\le 10^5,m\le 3\times 10^5\)。保证没有重边和自环。

考虑将两两有边的点缩成一个集合,则连向这个集合的点连向这个集合的所有点。并且若两个集合之间通过两条不同方向的边相连,则可以合并成一个大集合。每个集合对答案的贡献与它的大小和连向它的边数有关。

使用 set 维护每个集合的入边和出边,加边时若需要合并两个集合则将小集合的所有边加到大集合上。由于加边时可能发生连锁反应,可以用递归的方式加边。时间复杂度 \(O(n\log^2n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define PB push_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 100003, mod = 1e9 + 7;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, m, fa[N], siz[N]; set<int> in[N]; set<pii> out[N]; LL ans;
int get(int x){return x == fa[x] ? x : fa[x] = get(fa[x]);}
void add(int u, int v){
    int fu = get(u), fv = get(v);
    if(fu == fv || in[fv].count(u)) return;
    auto it = out[fv].lower_bound(MP(fu, 0));
    if(it == out[fv].end() || it->fi != fu){
        ans += siz[fv]; in[fv].insert(u); out[fu].insert(MP(fv, u)); return;
    }
    if(in[fu].size() + out[fu].size() < in[fv].size() + out[fv].size()) swap(fu, fv);
    vector<int> t1; vector<pii> t2;
    for(pii it : out[fv]){in[it.fi].erase(it.se); ans -= siz[it.fi]; t2.PB(it);}
    for(int u : in[fv]){out[get(u)].erase(MP(fv, u)); t1.PB(u);}
    ans += (2ll * siz[fu] + in[fu].size() - in[fv].size()) * siz[fv];
    out[fv].clear(); in[fv].clear();
    siz[fu] += siz[fv]; fa[fv] = fu;
    for(int u : t1) add(u, fu);
    for(pii it : t2) add(it.se, it.fi);
}
int main(){
    read(n); read(m);
    for(Rint i = 1;i <= n;++ i){fa[i] = i; siz[i] = 1;}
    for(Rint i = 1, u, v;i <= m;++ i){
        read(u); read(v); add(u, v); printf("%lld\n", ans);
    }
}

LOJ3033「JOISC 2019 Day2」两个天线

题目描述:给定三个长为 \(n\) 的正整数序列 \(h,a,b\)\(q\) 次询问 \(l,r\),求满足 \(l\le i,j\le r\)\(|i-j|\in[\max(a_i,a_j),\min(b_i,b_j)]\)\((i,j)\)\(|h_i-h_j|\) 的最大值。

数据范围:\(n,q\le 2\times 10^5,a_i\le b_i<n,h_i\le 10^9\)

一开始把区间交看成区间并了...就离谱(

按照 \(r\) 扫描线,可以固定 \(j=r\) 然后计算答案的时候求后缀 \(\max\)

\(h_i\) 有用的 \(r\) 是一个区间,对 \(r\) 有用的 \(h_i\) 也是一个区间,用维护历史最值的线段树即可。时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define Rint register int
#define fi first
#define se second
#define PB push_back
#define MP make_pair
using namespace std;
typedef vector<int> vi;
typedef pair<int, int> pii;
const int N = 200003, INF = 0x3f3f3f3f;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int n, m, h[N], a[N], b[N], mx[N<<2], tag[N<<2], omx[N<<2], ans[N]; vi st[N], ed[N];
struct Node {
    int l, r, id;
    Node(int _l = 0, int _r = 0, int _id = 0): l(_l), r(_r), id(_id){}
    bool operator < (const Node &o) const {
        if(r != o.r) return r < o.r;
        if(l != o.l) return l < o.l;
        return id < o.id;
    }
} q[N];
void pushup(int x){mx[x] = max(mx[x<<1], mx[x<<1|1]); omx[x] = max(omx[x<<1], omx[x<<1|1]);}
void pushtag(int x, int v){chmax(tag[x], v); chmax(omx[x], mx[x] + tag[x]);}
void pushdown(int x){if(tag[x] != -INF){pushtag(x<<1, tag[x]); pushtag(x<<1|1, tag[x]); tag[x] = -INF;}}
void upd(int x, int L, int R, int l, int r, int v){
    if(l <= L && R <= r){pushtag(x, v); return;}
    int mid = L + R >> 1; pushdown(x);
    if(l <= mid) upd(x<<1, L, mid, l, r, v);
    if(mid < r) upd(x<<1|1, mid+1, R, l, r, v);
    pushup(x);
}
void cle(int x, int L, int R, int p, int v){
    if(L == R){mx[x] = v; tag[x] = -INF; return;}
    int mid = L + R >> 1; pushdown(x);
    if(p <= mid) cle(x<<1, L, mid, p, v);
    else cle(x<<1|1, mid+1, R, p, v);
    pushup(x);
}
int qry(int x, int L, int R, int l, int r){
    if(l <= L && R <= r) return omx[x];
    int mid = L + R >> 1, res = -INF; pushdown(x);
    if(l <= mid) res = qry(x<<1, L, mid, l, r);
    if(mid < r) chmax(res, qry(x<<1|1, mid+1, R, l, r));
    return res;
}
void solve(){
    int cur = 1;
    for(Rint i = 1;i <= n;++ i){
        for(Rint p : st[i]) cle(1, 1, n, p, h[p]);
        for(Rint p : ed[i]) cle(1, 1, n, p, -INF);
        if(i > a[i]) upd(1, 1, n, max(i - b[i], 1), i - a[i], -h[i]);
        for(;cur <= m && q[cur].r <= i;++ cur) chmax(ans[q[cur].id], qry(1, 1, n, q[cur].l, q[cur].r));
    }
}
int main(){
    read(n); memset(ans, -1, sizeof ans);
    for(Rint i = 1;i <= n;++ i){
        read(h[i]); read(a[i]); read(b[i]);
        if(i+a[i]<=n) st[i+a[i]].PB(i);
        if(i+b[i]+1<=n) ed[i+b[i]+1].PB(i);
    } read(m);
    for(Rint i = 1;i <= m;++ i){read(q[i].l); read(q[i].r); q[i].id = i;}
    sort(q + 1, q + m + 1);
    memset(omx, -0x3f, sizeof omx); memset(mx, -0x3f, sizeof mx); memset(tag, -0x3f, sizeof tag);
    solve(); for(Rint i = 1;i <= n;++ i) h[i] = INF - h[i];
    memset(omx, -0x3f, sizeof omx); memset(mx, -0x3f, sizeof mx); memset(tag, -0x3f, sizeof tag);
    solve(); for(Rint i = 1;i <= m;++ i) printf("%d\n", ans[i]);
}

LOJ3041「JOISC 2019 Day4」矿物

这是一道交互题

题目描述:给定 \(2n\) 个元素,有 \(n\) 种,每种有 \(2\) 个。初始有集合 \(S=\varnothing\),每次你可以往里面增加一个元素或删除一个元素,然后询问 \(S\) 中有多少种元素。求所有相同种的元素对。

数据范围:\(n\le 43000\),操作次数 \(\le 10^6\)

注意到一次询问的信息是 1bit,得到的信息是当前加入/删除的元素的另一个同种元素是否在集合内。

于是按顺序询问每一个数可以将元素分为两组,每一组之间没有同种元素。考虑使用排序的方法将一组的元素与另一组按顺序对应。

设两组为 \(a,b\),考虑分治,首先询问左半边中的 \(a\) 元素,然后询问整个区间的 \(b\) 元素,就可以知道每个 \(b_i\) 所对应的同种元素在 \(a\) 的左半边还是右半边。然后即可递归下去。

若分治时平均分为两段,则只能拿到 90 分(\(\frac{3}{2}n\log_2n+2n\)),但注意到操作次数是 左半边长度+总长。于是考虑不平均分段。设 \(T(n)\) 为操作次数\(/n\),不妨设 \(T(n)=T(xn)+T((1-x)n)+x+1=k\log n+O(1)\)

\[\begin{aligned} k\log n&=xk\log xn+(1-x)k\log(1-x)n+x+1 \\ k\log n&=xk\log n+(1-x)k\log n+xk\log x+(1-x)k\log(1-x)+x+1 \\ -x-1&=xk\log x+(1-x)k\log (1-x) \\ k=f(x)&=-\frac{x+1}{x\log x+(1-x)\log(1-x)} \end{aligned} \]

\(f'(x)=0\)\(\log x=2\log(1-x)\),于是极值点仅有 \(x=\frac{3-\sqrt 5}{2}\),就可以拿到 100 分(\(1.44n\log_2n+2n\)

#include<bits/stdc++.h>
#include"minerals.h"
#define Rint register int
using namespace std;
const int N = 43003;
const double p = (3 - sqrt(5)) / 2;
namespace {
int a[N], b[N], c[N];
bool ask(int x){
    static int lst = 0;
    int now = Query(x); bool res = now ^ lst;
    lst = now; return res;
}
void work(int l, int r, bool o){
    if(l >= r) return;
    int mid = l + max((int)((r-l+1) * p) - 1, 0), t1 = l-1, t2 = mid;
    for(Rint i = l;i <= mid;++ i) ask(a[i]);
    for(Rint i = l;i <= r;++ i)
        if(t1 == mid) c[++ t2] = b[i];
        else if(t2 == r) c[++ t1] = b[i];
        else if(o ^ ask(b[i])) c[++ t2] = b[i];
        else c[++ t1] = b[i];
    for(Rint i = l;i <= r;++ i) b[i] = c[i];
    work(l, mid, !o); work(mid+1, r, o);
}
void solve(int n){
    int t1 = 0, t2 = 0;
    for(Rint i = 1;i <= (n<<1);++ i){
        if(ask(i)) a[++ t1] = i; else b[++ t2] = i;
    }
    work(1, n, 1);
    for(Rint i = 1;i <= n;++ i) Answer(a[i], b[i]);
}}
void Solve(int n){solve(n);}

LOJ3032「JOISC 2019 Day1」馕

题目描述:给定长为 \(l\text{cm}\) 的馕,第 \(i\text{cm}\) 是第 \(i\) 种风味的,有 \(n\) 个人分馕,第 \(i\) 个人吃长为 \(x\text{cm}\) 的风味为 \(j\) 的馕可以获得 \(xv_{i,j}\) 的幸福度,你需要构造一种将馕切成 \(n\) 段然后任意分配给这 \(n\) 个人的方案,使得每个人获得的幸福度不小于他独吞整个馕所获得幸福度的 \(\frac{1}{n}\)

数据范围:\(n,l\le 2000,v_{i,j}\le 10^5\)

预处理出每个人将馕分为 \(n\) 段幸福度相等的断点。每次找到还没有被分馕的人中,幸福度最小的断点,切给那个人即可,因为这一段的左端点必定不大于当前人的左端点。时间复杂度 \(O(nl)\)被打爆了

LOJ3036「JOISC 2019 Day3」指定城市

题目描述:给定 \(n\) 个点的树,每条边要建设双向车道,各有权值。\(q\) 次询问正整数 \(m\),你可以指定 \(m\) 个点为特殊点,求满足 所有特殊点都在以 \(b\) 为根 \(a\) 的子树内 的车道 \(a\rightarrow b\) 的权值之和的最小值。

数据范围:\(n\le 2\times 10^5\)

根据 pb 的做法,可以直接上树形 dp: \(dp_{x,i}\) 表示 \(x\) 子树中选 \(i\) 个,且子树外至少有一个特殊点,子树内的边的贡献最小值。发现这个东西是凸的,于是可以用可并堆维护差分。统计答案的时候点分治,钦定根必须在虚树中,然后这么做即可。时间复杂度 \(O(n\log^2n)\)

根据神奇结论,设 \(m=i\) 时选定的点集为 \(S_i\),则当 \(i\ge 2\) 时,\(S_i\subseteq S_{i+1}\)。这个结论其实跟上面的 dp 值的凸性等价(取的最大差分就是贪心选择使代价最大的点),由于根需要特判,所以 \(i\ge 2\) 时才正确。

「JOISC 2019 Day2」Two Transportations

这是一道通信题

题目描述:给定两个 \(n\) 个点的无向图,边带权,Alice 和 Bob 分别知道一张图的两部分边,需要让 Alice 求出 \(0\) 出发到所有点的最短路。两人之间可以互发 \(29n\) 个 bit 的信息。

数据范围:\(n\le 2000,m\le 5\times 10^5,w_i\le 500\)

显然是不能传送边的信息的,于是考虑两人分别跑最短路,并不断更新。使用 Dijkstra,两人每次给出最小的 \(dis\),然后用较小的更新较大的,再做松弛操作。

首先,传送 \(dis\) 的时候,由于每次都不超过上次+边权,于是只需要 \(9\) 个 bit。传送一个顶点需要 \(11\) 个 bit。于是考虑 \(A,B\) 先互相发送最小 \(dis\) 减上一次的,然后其中较小的向另一个发送顶点编号。

#include"Azer.h"
#include<bits/stdc++.h>
#define Rint register int
#define fi first
#define se second
#define MP make_pair
using namespace std;
typedef vector<int> vi;
typedef pair<int, int> pii;
namespace {
const int N = 2003, M = 1000003;
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, cnt = -1, head[N], to[M], nxt[M], w[M], dis[N], mxdis, ans, val, nod; bool vis[N];
pii find(){
    pii res = MP(mxdis + 501, 0);
    for(Rint i = 0;i < n;++ i) if(!vis[i]) chmin(res, MP(dis[i], i));
    res.fi -= mxdis; return res;
}
void add(int a, int b, int c){
    static int cnt = 0;
    to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt; w[cnt] = c;
}
void upd(int u, int val){
    mxdis = dis[u] = val; vis[u] = true;
    for(Rint i = head[u];i;i = nxt[i]) chmin(dis[to[i]], val + w[i]);
}}
void InitA(int n, int m, vi u, vi v, vi c){
    ::n = n; memset(dis, 0x3f, sizeof dis);
    for(Rint i = 0;i < m;++ i){add(u[i], v[i], c[i]); add(v[i], u[i], c[i]);}
    upd(0, 0);
}
void ReceiveA(bool x){
    do {
        if(ans == n-1) return;
        if(cnt == -1){
            pii tmp = find(); cnt = 0;
            for(Rint i = 0;i < 9;++ i) SendA(tmp.fi >> i & 1);
        } else if(cnt < 9){
            val |= x<<cnt; ++ cnt;
            if(cnt == 9){
                pii tmp = find();
                if(val > tmp.fi){
                    for(Rint i = 0;i < 11;++ i) SendA(tmp.se >> i & 1);
                    upd(tmp.se, tmp.fi + mxdis); ++ ans; cnt = -1; val = 0;
                } else ++ cnt;
            }
        } else {
            nod |= x<<cnt-10; ++ cnt;
            if(cnt == 21){
                upd(nod, mxdis + val); nod = val = 0; cnt = -1; ++ ans;
            }
        }
    } while(cnt == -1);
}
vi Answer(){
    vi res(n);
    for(Rint i = 0;i < n;++ i) res[i] = dis[i];
    return res;
}
#include"Baijan.h"
#include<bits/stdc++.h>
#define Rint register int
#define fi first
#define se second
#define MP make_pair
using namespace std;
typedef vector<int> vi;
typedef pair<int, int> pii;
namespace {
const int N = 2003, M = 1000003;
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, cnt, head[N], to[M], nxt[M], w[M], dis[N], mxdis, val, nod; bool vis[N];
pii find(){
    pii res = MP(mxdis + 501, 0);
    for(Rint i = 0;i < n;++ i) if(!vis[i]) chmin(res, MP(dis[i], i));
    res.fi -= mxdis; return res;
}
void add(int a, int b, int c){
    static int cnt = 0;
    to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt; w[cnt] = c;
}
void upd(int u, int val){
    mxdis = dis[u] = val; vis[u] = true;
    for(Rint i = head[u];i;i = nxt[i]) chmin(dis[to[i]], val + w[i]);
}}
void InitB(int n, int m, vi u, vi v, vi c){
    ::n = n; memset(dis, 0x3f, sizeof dis);
    for(Rint i = 0;i < m;++ i){add(u[i], v[i], c[i]); add(v[i], u[i], c[i]);}
    upd(0, 0); SendB(true);
}
void ReceiveB(bool x){
    if(cnt < 9){
        val |= x<<cnt; ++ cnt;
        if(cnt == 9){
            pii tmp = find();
            for(Rint i = 0;i < 9;++ i) SendB(tmp.fi >> i & 1);
            if(val >= tmp.fi){
                for(Rint i = 0;i < 11;++ i) SendB(tmp.se >> i & 1);
                upd(tmp.se, tmp.fi + mxdis); cnt = val = 0;
            } else ++ cnt;
        }
    } else {
        nod |= x<<cnt-10; ++ cnt;
        if(cnt == 21){upd(nod, val + mxdis); cnt = val = nod = 0;}
    }
}

LOJ3039「JOISC 2019 Day4」蛋糕拼接 3

题目描述:给定 \(n\) 个正整数 pair \((v_i,c_i)\) 和正整数 \(m\),求所有选择 \(m\) 个二元组排成环的方案中,\(\sum v_i-\sum|c_i-c_{i+1}|\) 的最大值。

数据范围:\(3\le m\le n\le 2\times 10^5,v_i,c_i\le 10^9\)

首先按照 \(c_i\) 排个序,那么 \(\sum |c_i-c_{i+1}|=2(\max c-\min c)\)。枚举 \([l,r]\) 表示最值,贡献为 \(v\) 的前 \(m\) 大之和减去 \(2(c_r-c_l)\),它显然满足四边形不等式,于是可以使用决策单调性优化。用主席树维护,时间复杂度 \(O(n\log^2n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 200003, M = N << 5;
template<typename T>
inline void read(T &x){
    int ch = getchar(); bool f = false; x = 0;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int n, m, val[N], len, rt[N], tot, siz[M], ls[M], rs[M];
LL sum[M], ans = -1e18; pii a[N];
void upd(int &x, int L, int R, int pos){
    ls[++tot] = ls[x]; rs[tot] = rs[x];
    sum[tot] = sum[x] + val[pos]; siz[tot] = siz[x] + 1; x = tot;
    if(L == R) return;
    int mid = L + R >> 1;
    if(pos <= mid) upd(ls[x], L, mid, pos); else upd(rs[x], mid+1, R, pos);
}
LL query(int x, int y, int L, int R, int k){
    if(L == R) return (LL) k * val[L];
    int mid = L + R >> 1, tmp = siz[rs[y]] - siz[rs[x]];
    if(k <= tmp) return query(rs[x], rs[y], mid+1, R, k);
    return sum[rs[y]] - sum[rs[x]] + query(ls[x], ls[y], L, mid, k - tmp);
}
void solve(int l, int r, int L, int R){
    int mid = l + r >> 1, pos = -1; LL tmp = -1e18;
    for(Rint i = max(L, mid+m-1);i <= R;++ i)
        if(chmax(tmp, query(rt[mid-1], rt[i], 0, len-1, m) - (a[i].fi - a[mid].fi << 1))) pos = i;
    chmax(ans, tmp);
    if(l < mid) solve(l, mid-1, L, pos);
    if(mid < r) solve(mid+1, r, pos, R);
}
int main(){
    read(n); read(m);
    for(Rint i = 0;i < n;++ i){read(a[i].se); read(a[i].fi); val[i] = a[i].se;}
    sort(a, a + n); sort(val, val + n); len = unique(val, val + n) - val;
    for(Rint i = 0;i < n;++ i){
        a[i].se = lower_bound(val, val + len, a[i].se) - val;
        if(i) rt[i] = rt[i-1]; upd(rt[i], 0, len-1, a[i].se);
    }
    solve(0, n-m, 0, n-1); printf("%lld\n", ans);
}

LOJ3281「JOISC 2020 Day4」传奇团子师傅

这是一道提交答案题

题目描述:给定 \(n\times m\) 的方格,每个格子有 \(3\) 种颜色(\(\text{A,B,C}\)),你每次可以取出 \(3\) 个方格,这些方格必须是在行或列或对角线上连续,且顺序为 \(\text{ABC}\)\(\text{CBA}\),求可以取出多少次,并构造方案。

LOJ3278「JOISC 2020 Day3」收获

题目描述:IOI 庄园有 \(n\) 个员工,\(m\) 棵苹果树种在湖岸。湖的周长为 \(l\) 米。

一开始员工 \(i\) 位于从湖的最北端向顺时针方向前进 \(a_i\) 米处,苹果树 \(j\) 生长在从湖的最北端向顺时针方向前进 \(b_j\) 米处。

每棵苹果树最多长一个苹果,收获后 \(c\) 秒会长出一个新的。时刻 \(0\) 时,所有的苹果树上都有一个苹果。员工从时刻 开始从各自的地点以 \(1\text{m/s}\) 的速度顺时针前进,遇到成熟的苹果就将其摘下(若到达时刚长出苹果,也要摘下),摘苹果的时间忽略不计。

现给出 个询问,第 \(k\) 次询问员工 \(v_k\) 在时刻 \(t_k\) 结束时一共收获到几个苹果。

数据范围:\(1\le n,m\le 2\times 10^5,0\le a_i<a_{i+1}<l,0\le b_i<b_{i+1}<l,1\le c\le 10^9,n+m\le l\le 10^9,1\le t_k\le 10^{18}\)

第一步就没想到?

考虑从人顺时针运动变为树逆时针运动。

这个采摘果树的过程可以建图!

将每个果树连向逆时针遇到的第一个人,表示被这个人第一次采摘。

将每个人连向第一个逆时针距离他 \(\ge C\) 的人,表示若这个人采摘一次,下一次由那个人采摘。

由于所有点出度为 \(1\),所以形成一个基环内向树,叶子表示树的初始位置。询问就变为了每棵树从叶子开始运动,经过 \(t_k\) 时刻之后节点 \(v_k\) 被经过多少次。

便于处理,我们先不考虑环上的一条边,变成一棵树,分类讨论果树是否经过环上的特殊边...

LOJ3034「JOISC 2019 Day2」两道料理

题目描述:厨师比太郎正在参加一个厨艺比赛。在这场比赛中参赛者要烹饪两道料理:IOI 盖饭和 JOI 咖喱。

IOI 盖饭的烹饪过程中需要 \(n\) 个步骤。第 \(i\) 步的用时是 \(a_i\) 分钟,想要进行第 \(i\) 步的条件是已经完成了第 \(i-1\) 步。

JOI 咖喱的烹饪过程中需要 \(m\) 个步骤。第 \(i\) 步的用时是 \(b_i\) 分钟,想要进行第 \(i\) 步的条件是已经完成了第 \(i-1\) 步。

做料理过程中需要专心致志,所以当他开始进行一个步骤时,就不能中断。当完成了一个步骤,他也可以选择进行另一道料理的下一个步骤。比赛开始后,在两道料理都完成之前,他不能停下来休息。

在这场比赛中,参赛者会按照接下来的方式得到艺术感的打分:

  • 如果他在比赛的前 \(s_i\) 分钟内完成了 IOI 盖饭的第 \(i\) 个步骤,那么从中他会得到 \(p_i\) 点的分数。
  • 如果他在比赛的前 \(t_i\) 分钟内完成了 JOI 咖喱的第 \(i\) 个步骤,那么从中他会得到 \(q_i\) 点的分数。

请你帮助比太郎设计做料理过程,最大化他做料理的艺术感评分。

数据范围:\(n,m\le 10^6,a_i,b_i\le 10^9,s_i,t_i\le 2\times 10^{15},|p_i|,|q_i|\le 10^9\)

预处理出 \(x_i\) 表示在 \(s_i\) 分钟内完成 IOI 盖饭的第 \(i\) 个步骤时,至多能完成 JOI 咖喱的 \(x_i\) 个步骤。\(y_j\) 同理。

那么就变成了从 \((0,0)\)\((m,n)\) 的一条向上/右的折线,若 \((x_i,i)\) 在折线上或下方,获得 \(p_i\) 的分数,若 \((j,y_j)\) 在折线上或上方,获得 \(q_j\) 的分数。

对第二种反面考虑,先将答案加上 \(q_j\),变为若 \((j-1,y_j+1)\) 在折线上或下方,获得 \(-q_j\) 的分数。

这个显然可以用 dp 直接做,\(dp_{i,j}=\max(dp_{i,j-1},dp_{i-1,j}+sum_{i-1,j})\)

使用 set 维护差分数组,时间复杂度 \(O((n+m)\log n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define PB push_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 1000003;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
int n, m, a[N], p[N], b[N], q[N], x[N], y[N]; LL sa[N], sb[N], s[N], t[N], ans, dp[N];
set<int> S; vector<pii> vec[N];
int main(){
    read(n); read(m);
    for(Rint i = 1;i <= n;++ i){read(a[i]); read(s[i]); read(p[i]); s[i] -= sa[i] = sa[i-1] + a[i];}
    for(Rint i = 1;i <= m;++ i){read(b[i]); read(t[i]); read(q[i]); t[i] -= sb[i] = sb[i-1] + b[i]; if(t[i] >= 0) ans += q[i];}
    for(Rint i = 1;i <= n;++ i)if(s[i] >= 0){x[i] = upper_bound(sb + 1, sb + m + 1, s[i]) - sb - 1; if(x[i] == m) ans += p[i]; else vec[x[i]].PB(MP(i, p[i]));}
    for(Rint i = 1;i <= m;++ i)if(t[i] >= 0){y[i] = upper_bound(sa + 1, sa + n + 1, t[i]) - sa - 1; if(y[i] < n) vec[i-1].PB(MP(y[i]+1, -q[i]));}
    for(Rint i = 0;i < m;++ i){
        for(pii p : vec[i]){dp[p.fi] += p.se; S.insert(p.fi);}
        for(pii p : vec[i]){
            auto l = S.lower_bound(p.fi), r = l; LL tmp = 0;
            while(r != S.end()){
                if((dp[*r] += tmp) > 0) break;
                tmp = dp[*r]; dp[*r++] = 0;
            } S.erase(l, r);
        }
    }
    for(Rint i = 1;i <= n;++ i) ans += dp[i];
    printf("%lld\n", ans);
}

LOJ3256「JOI 2020 Final」火灾

题目描述:给定长为 \(n\) 的正整数序列 \(a_i\)\(q\) 次询问 \(t,l,r\),求

\[\sum_{i=l}^r\max_{j=\max(1,i-t)}^{i}a_j \]

数据范围:\(n,q\le 2\times 10^5,a_i\le 10^9\)

看到区间求 \(\max\),想到用笛卡尔树维护。对每个 \(i\) 求出左/右第一个大于它的点 \(l_i,r_i\),考虑 \(a_{l_i}\)\([i,r_i)\) 的影响。

建立平面直角坐标系,设 \((x,y)\) 上的值为 \(\max\limits_{i=x-y}^xa_i\),则 \(\{(x,y)|x\in[i,r_i),y\ge x-l_i\}\)\(a_{l_i}\)\(\max\)

不知为何,若从下向上更新,则每次取 \(\max\) 的部分值都相等。可以变为区域+。

拆分为直角三角形:\(\{(x,y)|x\ge a,y\ge x-b\}\),它对询问 \(\sum(\le p,t)\) 的贡献次数为 \(\min(t+b,p)-\min(p,a-1)\)。由于询问是前缀差分,所以可以均减去 \(t\),变为 \(\min(b,p-t)-\min(a-1,p)\)

维护 \(\min(x,y)\) 这种东西,其中 \(x\) 只与修改有关,\(y\) 只与询问有关,可以使用一个前缀+,求前缀和的数据结构来做。上树状数组即可,时间复杂度 \(O((n+q)\log n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 200003;
int n, m, a[N], l[N], r[N], st[N], tp; LL sum[N], ans[N];
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
struct Query {
    int l, r, id;
    Query(int l = 0, int r = 0, int id = 0): l(l), r(r), id(id){}
};
vector<Query> q[N];
vector<pii> vec[N];
int EI(int x){return x & -x;}
struct BIT {
    LL a[N<<1];
    void upd(int p, LL v){for(;p <= (n<<1);p += EI(p)) a[p] += v;}
    LL qry(int p){LL res = 0; for(;p;p -= EI(p)) res += a[p]; return res;}
};
struct BBIT {
    BIT s[2];
    void upd(int p, int v){p += n; s[0].upd(1, v); s[0].upd(p+1, -v); s[1].upd(p+1, (LL)v*p);}
    LL qry(int p){p += n; return s[0].qry(p) * p + s[1].qry(p);}
} bt[2];
int main(){
    read(n); read(m);
    for(Rint i = 1;i <= n;++ i){read(a[i]); sum[i] = sum[i-1] + a[i];}
    for(Rint i = 1, t, l, r;i <= m;++ i){read(t); read(l); read(r); --l; ans[i] = sum[r] - sum[l]; q[t].PB(Query(l, r, i));}
    for(Rint i = 1;i <= n;++ i){
        while(tp && a[i] >= a[st[tp]]) r[st[tp--]] = i;
        l[i] = st[tp]; st[++tp] = i;
    }
    for(Rint i = 1;i <= n;++ i) if(l[i]){
        vec[i - l[i]].PB(MP(i, a[l[i]] - a[i]));
        if(r[i]) vec[r[i] - l[i]].PB(MP(r[i], a[i] - a[l[i]]));
    }
    for(Rint i = 1;i <= n;++ i){
        for(pii t : vec[i]){bt[0].upd(t.fi - i, t.se); bt[1].upd(t.fi - 1, t.se);}
        for(Query t : q[i]) ans[t.id] += bt[0].qry(t.r - i) - bt[0].qry(t.l - i) + bt[1].qry(t.l) - bt[1].qry(t.r);
    }
    for(Rint i = 1;i <= m;++ i) printf("%lld\n", ans[i]);
}

LOJ2350「JOI 2018 Final」月票购买

题目描述:给定 \(n\) 个点 \(m\) 条边和四个顶点 \(s,t,u,v\),边带权,你可以选择一条 \(s\)\(t\) 的最短路,将这条路径上的边权变为 \(0\),求 \(dis(u,v)\) 最小值。

数据范围:\(n\le 10^5,m\le 2\times 10^5\),权值 \(\in [1,10^9]\)

第一个包都不会,人都傻了

有一个结论,\(u\)\(v\) 的最短路经过的免费边一定是连续的。

建出 \(s\)\(t\) 的最短路径 DAG,求出 \(f_i\)\(t_i\) 表示只经过最短路径 DAG 上的边,能到达的所有点 \(j\)\(dis(u,j)\)\(dis(j,v)\) 的最小值,就能直接计算答案。时间复杂度 \(O(m\log m)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<LL, int> pii;
const int N = 100003, M = N<<2;
int n, m, s, t, u, v, cnt = 1, head[N], to[M], nxt[M], w[M];
vector<int> E[N], ord;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
void add(int a, int b, int c){to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt; w[cnt] = c;}
LL dis[4][N], f[2][N], ans; bool vis[N];
priority_queue<pii, vector<pii>, greater<pii> > pq;
void solve(int S, LL *dis){
    for(Rint i = 1;i <= n;++ i){dis[i] = 1e18; vis[i] = false;}
    dis[S] = 0; pq.push(MP(0, S));
    while(!pq.empty()){
        int now = pq.top().se; pq.pop();
        if(vis[now]) continue; vis[now] = true;
        for(Rint i = head[now];i;i = nxt[i])
            if(chmin(dis[to[i]], dis[now] + w[i])) pq.push(MP(dis[to[i]], to[i]));
    }
}
void dfs(int x){
    if(vis[x]) return; vis[x] = true;
    for(Rint v : E[x]) dfs(v); ord.PB(x);
}
int main(){
    read(n); read(m); read(s); read(t); read(u); read(v);
    for(Rint i = 1, a, b, c;i <= m;++ i){
        read(a); read(b); read(c); add(a, b, c); add(b, a, c);
    }
    solve(s, dis[0]); solve(t, dis[1]); solve(u, dis[2]); solve(v, dis[3]);
    for(Rint i = 2;i <= cnt;++ i)
        if(dis[0][to[i^1]] + w[i] + dis[1][to[i]] == dis[0][t]) E[to[i^1]].PB(to[i]);
    memset(vis, 0, sizeof vis); dfs(s); ans = dis[2][v];
    for(Rint a : ord){
        f[0][a] = dis[2][a]; f[1][a] = dis[3][a];
        for(Rint b : E[a]){chmin(f[0][a], f[0][b]); chmin(f[1][a], f[1][b]);}
        chmin(ans, min(f[0][a] + dis[3][a], f[1][a] + dis[2][a]));
    }
    printf("%lld\n", ans);
}

LOJ2344「JOI 2016 Final」铁路票价

题目描述:给定 \(n\) 个点 \(m\) 条边的无向简单连通图,初始边权都为 \(1\)\(q\) 次询问一条边,将它的长度由 \(1\) 改为 \(2\),然后求与原图相比,有多少节点到 \(1\) 的最短路长度改变了。

数据范围:\(n\le 10^5,m\le 2\times 10^5\),保证每次询问的边不同。

先求出以 \(1\) 为根的最短路 DAG,然后每次删掉一条边 \((u,v)\),若此时点 \(x\)\(1\) 不连通了,则 \(x\) 会改变最短路长度。

维护这个东西,可以通过维护每个点的入度,每次删边使 \(v\) 的入度为 \(0\) 则把 \(v\) 删掉,将 \(v\) 连出的边也都删掉。

注意在删边之前判断这条边是否已经被删过。

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
const int N = 100003, M = N<<2;
int n, m, Q, cnt, a[M], b[M], head[N], to[M], nxt[M], q[N], dep[N], in[N], f, r, ans;
bool vis[M];
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
inline void add(int a, int b){to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt;}
void work(int x){
    ++ ans;
    for(Rint i = head[x];i;i = nxt[i])
        if(!vis[i+1>>1] && dep[to[i]] == dep[x] + 1){
            vis[i+1>>1] = true;
            if(!--in[to[i]]) work(to[i]);
        }
}
int main(){
    read(n); read(m); read(Q);
    for(Rint i = 1;i <= m;++ i){
        read(a[i]); read(b[i]); add(a[i], b[i]); add(b[i], a[i]);
    }
    dep[q[0] = r = 1] = 1;
    while(f < r){
        int x = q[f ++];
        for(Rint i = head[x];i;i = nxt[i])
            if(!dep[to[i]]){dep[to[i]] = dep[x] + 1; q[r++] = to[i];}
    }
    for(Rint i = 1;i <= n;++ i)
        for(Rint j = head[i];j;j = nxt[j]) if(dep[to[j]] == dep[i] + 1) ++ in[to[j]];
    while(Q --){
        int t; read(t);
        if(vis[t]){printf("%d\n", ans); continue;} vis[t] = true;
        if(dep[a[t]] > dep[b[t]]) swap(a[t], b[t]);
        if(dep[a[t]] < dep[b[t]] && !--in[b[t]]) work(b[t]);
        printf("%d\n", ans);
    }
}

LOJ3013「JOI 2019 Final」硬币收藏

题目描述:给定平面上 \(2n\) 个硬币 \((x_i,y_i)\),你每次可以将硬币向四方向移动一个单位长度。你要将其移动到 \(x\in[1,n]\cap\Z,y\in\{1,2\}\)\((x,y)\) 上。过程中允许两个硬币在同一个点上,求最小移动次数。

数据范围:\(n\le 10^5,|x_i|,|y_i|\le 10^9\)

有一堆结论:

  1. 可以将硬币先移到 \(\{1,2\}\times([1,n]\cap\Z)\) 中,显然不劣。
  2. 考虑硬币和目标位置的匹配,移动目标位置和移动硬币显然是一样的。

然后考虑从左向右贪心,先将同一位置的匹配了,然后匹配上下之间,剩下的向右移动。时空复杂度 \(O(n)\)

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = 100003;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
int n, f[2][N]; LL ans;
int main(){
    read(n);
    for(Rint i = 1, x, y, xx, yy;i <= (n<<1);++ i){
        read(x); read(y); -- y; xx = min(n, max(1, x)); yy = y > 0;
        ans += abs(xx - x) + abs(yy - y); ++ f[yy][xx];
    }
    for(Rint i = 1, x;i <= n;++ i){
        -- f[0][i]; -- f[1][i];
        if(f[0][i] > 0 && f[1][i] < 0){
            x = min(f[0][i], -f[1][i]);
            f[0][i] -= x; f[1][i] += x; ans += x;
        } else if(f[0][i] < 0 && f[1][i] > 0){
            x = min(-f[0][i], f[1][i]);
            f[0][i] += x; f[1][i] -= x; ans += x;
        }
        f[0][i+1] += f[0][i]; ans += abs(f[0][i]);
        f[1][i+1] += f[1][i]; ans += abs(f[1][i]);
    } printf("%lld\n", ans);
}

LOJ2759「JOI 2014 Final」飞天鼠

题目描述:给定 \(n\) 棵树,第 \(i\) 棵树的高度是 \(h_i\)。你初始在第 \(1\) 棵树上,距离地面 \(X\) 米。你可以在 \(m\) 对树之间飞行,若你当前距离地面 \(h\) 米,飞行时间为 \(t\) 秒,则飞过去之后距离地面 \(h-t\) 米,若 \(h-t<0\)\(h-t\) 大于树高则无法飞行。你也可以花费 \(1\text{s}\) 向上/下 \(1\) 米。求到达第 \(n\) 棵树的顶端的最小花费时间。

数据范围:\(n\le 10^5,m\le 3\times 10^5,h_i\le 10^9,0\le X\le h_1\)

有一个结论,由于上升和下降的代价是一样的,所以若当前飞行的高度合法,则不会上升/下降,否则会导致之后的决策更劣。

于是到达每个点的高度是不需决策的,可以求出来。直接 dij 即可,时间复杂度 \(O(m\log m)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define se second
using namespace std;
typedef long long LL;
typedef pair<LL, int> pii;
const int N = 100003, M = N*6;
const LL inf = 0x3f3f3f3f3f3f3f3fll;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, m, h[N], he[N], head[N], to[M], nxt[M], w[M]; LL dis[N];
void add(int a, int b, int c){
    static int cnt = 0;
    to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt; w[cnt] = c;
}
priority_queue<pii, vector<pii>, greater<pii> > pq;
bool vis[N];
int main(){
    read(n); read(m); read(he[1]);
    for(Rint i = 1;i <= n;++ i) read(h[i]);
    for(Rint i = 1, a, b, c;i <= m;++ i){
        read(a); read(b); read(c);
        if(c <= h[a]) add(a, b, c);
        if(c <= h[b]) add(b, a, c);
    }
    memset(dis, 0x3f, sizeof dis); pq.push(MP(dis[1] = 0, 1));
    while(!pq.empty()){
        int x = pq.top().se; pq.pop(); if(vis[x]) continue; vis[x] = true;
        for(Rint i = head[x];i;i = nxt[i]){
            int tmp = max(min(he[x] - w[i], h[to[i]]), 0);
            if(chmin(dis[to[i]], dis[x] + w[i] + abs(tmp + w[i] - he[x]))){
                he[to[i]] = tmp; pq.push(MP(dis[to[i]], to[i]));
            }
        }
    }
    if(dis[n] >= inf) puts("-1");
    else printf("%lld\n", dis[n] + h[n] - he[n]);
}

LOJ3282「JOISC 2020 Day4」治疗计划

题目链接:LOJ

若有两个治疗方案 \((L_i,R_i,T_i)\)\((L_j,R_j,T_j)\),则能合并为一个区间当且仅当 \(R_i-L_j+1\ge |T_i-T_j|\)。目标就是合并出 \([1,n]\)。考虑建图,原点向所有 \(L_i=1\)\(i\) 连边,权值 \(c_i\),汇点同理。若 \(i\) 能与 \(j\) 合并则

将不等式中的绝对值拆开:

  1. \(T_i\le T_j\),则 \(R_i+T_i+1\ge T_j+L_j\)
  2. \(T_i>T_j\),则 \(R_i-T_i+1\ge L_j-T_j\)

将所有治疗方案按照 \(T_i\) 排序。使用两棵线段树维护 \(L_j\pm T_j\),由于每个点的所有入边权值相同,所以每个点只会被更新一次。时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define se second
using namespace std;
typedef long long LL;
typedef pair<LL, int> pii;
const int N = 100003;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
priority_queue<pii, vector<pii>, greater<pii> > pq;
int n, m; LL dis[N]; vector<int> E;
struct Node {
    int t, l, r, c;
    bool operator < (const Node &o) const {return t < o.t;}
} a[N];
struct SegTree {
    int minv[N<<2];
    void pushup(int x){minv[x] = min(minv[x<<1], minv[x<<1|1]);}
    void build(int x, int L, int R, int w){
        if(L == R){minv[x] = a[L].l + w * a[L].t; return;}
        int mid = L + R >> 1;
        build(x<<1, L, mid, w); build(x<<1|1, mid+1, R, w);
        pushup(x);
    }
    void qry(int x, int L, int R, int l, int r, int val){
        if(minv[x] > val) return;
        if(L == R){E.push_back(L); return;}
        int mid = L + R >> 1;
        if(l <= mid) qry(x<<1, L, mid, l, r, val);
        if(mid < r) qry(x<<1|1, mid+1, R, l, r, val);
    }
    void mdf(int x, int L, int R, int p){
        if(L == R){minv[x] = 2e9; return;}
        int mid = L + R >> 1;
        if(p <= mid) mdf(x<<1, L, mid, p);
        else mdf(x<<1|1, mid+1, R, p);
        pushup(x);
    }
} t[2];
int main(){
    read(n); read(m);
    for(Rint i = 1;i <= m;++ i){read(a[i].t); read(a[i].l); read(a[i].r); read(a[i].c);}
    sort(a + 1, a + m + 1);
    memset(dis, 0x3f, sizeof dis);
    t[0].build(1, 1, m, -1); t[1].build(1, 1, m, 1);
    for(Rint i = 1;i <= m;++ i)
        if(a[i].l == 1){
            pq.push(MP(dis[i] = a[i].c, i));
            t[0].mdf(1, 1, m, i); t[1].mdf(1, 1, m, i);
        }
    while(!pq.empty()){
        int x = pq.top().se; pq.pop(); E.clear();
        if(a[x].r == n){printf("%lld\n", dis[x]); return 0;}
        if(x > 1) t[0].qry(1, 1, m, 1, x - 1, a[x].r - a[x].t + 1);
        if(x < m) t[1].qry(1, 1, m, x + 1, m, a[x].r + a[x].t + 1);
        for(Rint v : E){
            pq.push(MP(dis[v] = dis[x] + a[v].c, v));
            t[0].mdf(1, 1, m, v); t[1].mdf(1, 1, m, v);
        }
    } puts("-1");
}

LOJ3012「JOI 2019 Final」有趣的家庭菜园 3

题目描述:给定长为 \(n\) 的字符串 \(S\),求最少的相邻交换次数,使得相邻两个字符不等。

数据范围:\(n\le 400,|\Sigma|=3\)

ntf说这事sb?

求相邻交换次数,相当于求逆序对个数,即原来与现在顺序不同的数对个数。直接 dp...?

时间复杂度 \(O(n^3)\)

CF Gym 102059 D Dumae

题目描述:给定 \(n\) 个区间 \([l_i,r_i]\)\(m\) 个二元组 \((u_i,v_i)\),构造长为 \(n\) 的排列 \(p\) 使得 \(p_i\in[l_i,r_i]\)\(p_{u_i}<p_{v_i}\)。需判断无解。

数据范围:\(n\le 3\times 10^5,m\le 10^6,1\le l_i\le r_i\le n,1\le u_i,v_i\le n\)

CF Gym 101741 H Compressed Spanning Subtrees

这事一道交互题

题目描述:交互器有一棵 \(n\) 个点的树,你至多询问 \(2550\) 次点集 \(X\),交互器会告诉你 \(X\) 的虚树大小。求这棵树。

数据范围:\(n\le 100\)

CF Gym 102129 C Medium Hadron Collider

这是一道交互题

\(p=2\times 10^7-1\)。交互器有一个自然数 \(a\le 10^7\),设 \(F(x)=-(x+1)^a(x-1)^{10^7-1-a}\),你可以至多询问 \(10\) 次正整数 \(k\in[2^7,2^9)\),交互器会告诉你 \([x^k]F(x)\bmod p\)。对于所有自然数 \(k<2^7\),求 \([x^k]F(x)\bmod p\)

\(F(x)=(x+1)^a(x-1)^b\),则 \(F'(x)(x^2-1)=F(x)((N-1)x+N-1-2a)\)

\((N-n)f_{n-1}+(n+1)f_{n+1}=(2a+1-N)f_n\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 1e7, mod = 19999999;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
void qmo(int &x){x += x >> 31 & mod;}
int ksm(int a, int b){int res = 1; for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod; return res;}
int f0, f1, f2, n, fac[N], inv[N];
int query(int x){printf("? %d\n", x+1); fflush(stdout); read(x); qmo(x = -x); return x;}
int C(int n, int m){if(m < 0 || n < m) return 0; return (LL) fac[n] * inv[m] % mod * inv[n-m] % mod;}
int main(){
    read(n); f0 = query(128); f1 = query(n = 129); f2 = query(130);
    for(;!f1;++ n){f0 = f1; f1 = f2; f2 = query(n+2);}
    int a = (((LL) (N-n) * f0 + (n+1ll) * f2) % mod * ksm(f1, mod-2) % mod + N - 1) % mod;
    if(a & 1) a += mod; a >>= 1; putchar('!'); fac[0] = 1;
    for(Rint i = 1;i < N;++ i) fac[i] = (LL) fac[i-1] * i % mod;
    inv[N-1] = ksm(fac[N-1], mod-2);
    for(Rint i = N-1;i;-- i) inv[i-1] = (LL) inv[i] * i % mod;
    for(Rint i = 0;i < 128;++ i){
        int res = 0;
        for(Rint j = 0;j <= i;++ j){
            int tmp = (LL) C(N-1-a, j) * C(a, i-j) % mod;
            if(a-j & 1) qmo(res -= tmp); else qmo(res += tmp - mod);
        }
        if(res >= N) res -= mod; printf(" %d", res);
    }
}

-LOJ2345「JOI 2016 Final」领地

题目描述:给定一个长为 \(n\) 的字符串 \(S\) 和正整数 \(k\),初始在 \((0,0)\),每次按照 \(S\) 中的字符移动,重复 \(k\) 次,求有多少个 \(1\times 1\) 正方形的四角都被经过。

数据范围:\(n\le 10^5,k\le 10^9\)

-CF1406E Deleting Numbers

这是一道交互题

题目描述:给定正整数 \(n\),交互器有一个正整数 \(x\le n\),初始有一个集合 \(S=[1,n]\cap\Z\),你可以询问至多 \(9999\) 次正整数 \(a\le n\) (1) \(S\)\(a\) 的倍数的个数,(2) \(S\)\(a\) 的倍数的个数,并将这些数中除了 \(x\) 之外的数删掉(要求 \(a\ge 2\))。求 \(x\)

数据范围:\(n\le 10^5\)

CF Gym 102354 Square Root Partitioningsss

题目描述:给定 \(n\) 个正整数 \(a_i\),求有多少个 \(\sqrt{a_1}\pm\sqrt{a_2}\pm\dots\pm\sqrt{a_n}=0\)

数据范围:\(n\le 36,a_i\le 10^{10^5}\)

\(b_i\) 表示 \(a_i\) 的最大非平方因子,则对于 \(b_i\) 不同的 \(a_i\) 之间互不影响,将 \(=0\) 的方案数相乘即可。

如何判断 \(b_i\) 相同,可以取 \(B\)\([10^{14},10^{14}+10^7]\) 的质数(这些质数没有整除任何一个 \(a_i\) 的),判断 \(a_i\) 的二次剩余存在性,假定存在性相同的数的 \(b_i\) 相同,错误概率 \(O(n^22^{-B})\)

至于计算答案,有 \(1-\frac{1}{2^B-1}\) 的概率找出一个质数 \(p\),满足这些数在 \(\text{mod} \ p\) 意义下都有二次剩余。在 \(\text{mod} \ p\) 意义下直接开根,用 meet-in-the-middle 计算即可。时间复杂度 \(O((n+B)\log a_i+n2^{\frac{n}{2}})\)

写代码的时候突然忘记Cipolla了,想到如果取出的质数 \(p\) 都是模 \(4\)\(3\) 的,就可以直接用 \(a^{\frac{p+1}{4}}\) 计算 \(\sqrt a\)

这就引出了 \(\color{red}{\text{Fulisike}}\)极简做法,设 \(p=10^{17}+3\),直接默认 \(\sqrt x=x^{\frac{p+1}{4}}\bmod p\) 来计算,因为若包含不同 \(b_i\) 的数,算出 \(0\) 的概率极小(\(O(\frac{2^n}{p})\)),但是可以被定向卡,可原题是 ACM...

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define PB push_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef long double LD;
typedef pair<int, int> pii;
const int N = 100003, K = 1e7 + 1, B = 30;
const LL V = 1e14;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
LL mul(LL a, LL b, LL mod){LL res = a * b - (LL)((LD) a / mod * b + 0.5) * mod; return (res % mod + mod) % mod;}
LL ksm(LL a, LL b, LL mod){
    LL res = 1;
    for(;b;b >>= 1, a = mul(a, a, mod)) if(b & 1) res = mul(res, a, mod);
    return res;
}
int n, tot, now[36], cnt; bool notp[K], np[K], vis[36];
LL sq[36], r[30][36], rem[36], pri[30], ans = 1;
char s[36][N]; vector<LL> pis; map<LL, int> S;
LL calc(int id, LL p){
    LL res = 0;
    for(const char *c = s[id];*c;++ c) res = (res * 10 + *c - '0') % p;
    return res;
}
int main(){
    srand(time(0)); notp[0] = notp[1] = true;
    for(Rint i = 2;i < K;++ i) if(!notp[i]){
        for(Rint j = i<<1;j < K;j += i) notp[j] = true;
        for(LL j = ((V-1)/i+1)*i;j < V+K;j += i) np[j-V] = true;
    }
    for(Rint i = 0;i < K;++ i) if(!np[i] && (i&3)==3) pis.PB(i+V);
    random_shuffle(pis.begin(), pis.end()); read(n);
    for(Rint i = 0;i < n;++ i) scanf("%s", s[i]);
    for(Rint i = 0, u = 0;i < B;++ u){
        bool f = true;
        for(Rint j = 0;f && j < n;++ j)
            if(!(r[i][j] = calc(j, pis[u]))) f = false;
        if(f) pri[i++] = pis[u];
    }
    for(Rint i = 0;i < n;++ i)
        for(Rint j = 0;j < B;++ j) if(ksm(r[j][i], pri[j]-1>>1, pri[j]) == 1) rem[i] |= 1 << j;
    for(Rint i = 0;i < n;++ i) if(!vis[i]){
        cnt = 0;
        for(Rint j = i;j < n;++ j) if(rem[i] == rem[j]){now[cnt++] = j; vis[j] = true;}
        int p = -1; S.clear();
        for(Rint j = 0;j < B;++ j) if(rem[i] >> j & 1){p = j; break;}
        for(Rint j = 0;j < cnt;++ j) sq[j] = ksm(r[p][now[j]], pri[p]+1>>2, pri[p]);
        int k = cnt>>1; LL res = 0;
        for(Rint i = 0;i < (1<<k);++ i){
            LL sum = 0;
            for(Rint j = 0;j < k;++ j)
                if(i>>j&1){sum += sq[j]; if(sum >= pri[p]) sum -= pri[p];}
                else {sum -= sq[j]; if(sum < 0) sum += pri[p];}
            ++ S[sum];
        }
        for(Rint i = 0;i < (1<<cnt-k);++ i){
            LL sum = 0;
            for(Rint j = 0;j < cnt-k;++ j)
                if(i>>j&1){sum += sq[j+k]; if(sum >= pri[p]) sum -= pri[p];}
                else {sum -= sq[j+k]; if(sum < 0) sum += pri[p];}
            res += S[sum];
        }
        ans *= res;
    }
    printf("%lld\n", ans>>1);
}

CF418E Tricky Password

题目描述:给定 \(n\) 列的矩阵 \(a\),第一行给出,\(a_{i,j}\)\(a_{i-1,j}\)\(a_{i-1,1},\dots,a_{i-1,j}\) 中的出现次数(\(i>1\))。\(q\) 次操作 (1) 第一行单点修改 (2) 单点查询。

数据范围:\(n,q\le 10^5\)

可以证明 \(a\) 从第二行开始周期为 \(2\)。因为从第二行开始,对于每个前缀,都有 \(i\) 的出现次数不小于 \(i-1\) 的出现次数。根据这个性质可得,两行之后变为它自己。

第一行显然,第二行可以用平衡树维护出现次数我偷懒写了个动态开点线段树,第三行 \(a_{3,p}\)\(a_{1,[1,p]}\) 中出现次数不小于 \(a_{2,p}\) 的数的种数,使用莫队维护桶和桶的桶即可。空间复杂度 \(O(n)\)\(O(n\log n)\),时间复杂度 \(O(n\sqrt n)\)

#include<bits/stdc++.h>
#define Rint register int
#define PB push_back
using namespace std;
typedef pair<int, int> pii;
const int N = 200003, M = N<<5;
template<typename T>
inline void read(T &x){
    int ch = getchar(); x = 0; bool f = false;
    for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    if(f) x = -x;
}
int n, m, B, a[N], opt[N], x[N], y[N], ans[N], tot, ctt, val[N], len;
struct Qry {
    int p, x, id;
    Qry(int _p = 0, int _x = 0, int _id = 0): p(_p), x(_x), id(_id){}
    inline bool operator < (const Qry &o) const {
        if(p / B != o.p / B) return p / B < o.p / B;
        if(p / B & 1) return id > o.id; return id < o.id;
    }
} q[N], c[N];
int rt[N], ls[M], rs[M], sum[M], nc;
void change(int &x, int L, int R, int p, int v){
    if(!x) x = ++ nc; sum[x] += v; if(L == R) return;
    int mid = L + R >> 1;
    if(p <= mid) change(ls[x], L, mid, p, v);
    else change(rs[x], mid+1, R, p, v);
}
int query(int x, int L, int R, int r){
    if(!x) return 0; if(R <= r) return sum[x];
    int mid = L + R >> 1, res = query(ls[x], L, mid, r);
    if(mid < r) res += query(rs[x], mid+1, R, r);
    return res;
}
int qr, qt, cnt[N], ccnt[N]; unordered_set<int> S;
void add(int x){-- ccnt[cnt[x]]; if(cnt[x] == B) S.insert(x); ++ cnt[x]; ++ ccnt[cnt[x]];}
void del(int x){-- ccnt[cnt[x]]; -- cnt[x]; ++ ccnt[cnt[x]]; if(cnt[x] == B) S.erase(x);}
int main(){
    read(n); B = sqrt(n) + 1; len = n;
    for(Rint i = 1;i <= n;++ i){read(a[i]); val[i-1] = a[i];} read(m);
    for(Rint i = 1;i <= m;++ i){
        read(opt[i]); read(x[i]); read(y[i]);
        if(opt[i] == 1) val[len++] = x[i];
    }
    sort(val, val + len); len = unique(val, val + len) - val;
    for(Rint i = 1;i <= n;++ i){
        a[i] = lower_bound(val, val + len, a[i]) - val;
        change(rt[a[i]], 1, n, i, 1);
    }
    for(Rint i = 1;i <= m;++ i)
        if(opt[i] == 1){
            x[i] = lower_bound(val, val + len, x[i]) - val;
            c[++ctt] = Qry(y[i], x[i], i);
            change(rt[a[y[i]]], 1, n, y[i], -1);
            change(rt[x[i]], 1, n, y[i], 1); swap(a[y[i]], x[i]);
        } else {
            if(x[i] == 1){ans[i] = val[a[y[i]]]; continue;}
            if(x[i] & 1){
                q[++tot] = Qry(y[i], query(rt[a[y[i]]], 1, n, y[i]), i);
            }else ans[i] = query(rt[a[y[i]]], 1, n, y[i]);
        }
    sort(q + 1, q + tot + 1);
    for(Rint i = m;i;-- i) if(opt[i] == 1) swap(a[y[i]], x[i]);
    for(Rint i = 1;i <= tot;++ i){
        while(qr < q[i].p) add(a[++ qr]);
        while(qr > q[i].p) del(a[qr --]);
        while(qt < ctt && c[qt+1].id <= q[i].id){++ qt; if(c[qt].p <= qr){del(a[c[qt].p]); add(c[qt].x);} swap(a[c[qt].p], c[qt].x);}
        while(qt && c[qt].id > q[i].id){if(c[qt].p <= qr){del(a[c[qt].p]); add(c[qt].x);} swap(a[c[qt].p], c[qt].x); -- qt;}
        int &ret = ans[q[i].id];
        if(q[i].x > B){for(Rint u : S) ret += cnt[u] >= q[i].x;}
        else {ret = S.size(); for(Rint j = q[i].x;j <= B;++ j) ret += ccnt[j];}
    }
    for(Rint i = 1;i <= m;++ i) if(opt[i] == 2) printf("%d\n", ans[i]);
}

CF804E The same permutation

题目描述:给定正整数 \(n\),求出 \((i,j)\)\(1\le i<j\le n\),总共 \(\frac{n(n-1)}{2}\) 个)的一个排列,使得对于任意一个长为 \(n\) 的排列 \(p\),按照这个顺序交换 \((p_i,p_j)\),最后得到它自己。需判断无解。

数据范围:\(n\le 1000\)

\(2\not|\frac{n(n-1)}2\)\(n\bmod 4\ge 2\),则逆序对数奇偶性不对,必定无解。

考虑手玩一下,\(n=4\) 时可以 \((1,2),(3,4),(1,4),(2,3),(1,3),(2,4)\)\(n=5\) 时可以 \(\dots,(1,5),(2,5),(1,3),(2,4),(4,5),(3,5)\)

\(n\) 个元素分为 \(\lfloor\frac{n}{4}\rfloor\) 组,每组 \(4\) 个元素。若多一个元素则把它加入每一组。考虑 \((1,2,3,4)\Rightarrow (2,1,4,3)\) 花费四步操作,则可以解决两组之间的 \(16\) 步。

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
int n, t[10];
void work0(){
    printf("%d %d\n", t[0], t[1]);
    printf("%d %d\n", t[2], t[3]);
    printf("%d %d\n", t[0], t[3]);
    printf("%d %d\n", t[1], t[2]);
    printf("%d %d\n", t[0], t[2]);
    printf("%d %d\n", t[1], t[3]);
}
void work1(){
    printf("%d %d\n", t[0], t[1]);
    printf("%d %d\n", t[2], t[3]);
    printf("%d %d\n", t[0], t[3]);
    printf("%d %d\n", t[1], t[2]);
    printf("%d %d\n", t[0], t[4]);
    printf("%d %d\n", t[1], t[4]);
    printf("%d %d\n", t[0], t[2]);
    printf("%d %d\n", t[1], t[3]);
    printf("%d %d\n", t[3], t[4]);
    printf("%d %d\n", t[2], t[4]);
}
void work2(){
    printf("%d %d\n", t[0], t[4]);
    printf("%d %d\n", t[1], t[5]);
    printf("%d %d\n", t[0], t[5]);
    printf("%d %d\n", t[1], t[4]);
    printf("%d %d\n", t[0], t[6]);
    printf("%d %d\n", t[1], t[7]);
    printf("%d %d\n", t[0], t[7]);
    printf("%d %d\n", t[1], t[6]);
    printf("%d %d\n", t[2], t[4]);
    printf("%d %d\n", t[3], t[5]);
    printf("%d %d\n", t[2], t[5]);
    printf("%d %d\n", t[3], t[4]);
    printf("%d %d\n", t[2], t[6]);
    printf("%d %d\n", t[3], t[7]);
    printf("%d %d\n", t[2], t[7]);
    printf("%d %d\n", t[3], t[6]);
}
int main(){
    scanf("%d", &n);
    if((n & 3) > 1){puts("NO"); return 0;} puts("YES");
    for(Rint i = 1;i < n;i += 4){
        for(Rint j = 0;j < 4;++ j) t[j] = i+j;
        if(n & 1){t[4] = n; work1();} else work0();
        for(Rint j = i + 4;j < n;j += 4){
            for(Rint k = 0;k < 4;++ k){t[k] = i+k; t[k+4] = j+k;}
            work2();
        }
    }
}

CF Gym 102443K RotationAlmostSort

题目描述:给定正整数 \(n\),对于所有 \(n\times n\) 的矩阵,你需要输出一个程序,包含不超过 \(10^5\) 条语句。语句形如 \(A>B?C\),其中 \(A,B\) 为两个格子,\(C\) 是一个 \(2\times 2\) 的子矩阵,表示若 \(A\) 中的数大于 \(B\) 中的数,则将 \(C\) 中的数逆时针旋转。你需要将后 \(n-2\) 行按从上到下,从左往右的顺序排序。

数据范围:\(3\le n\le 9\)

发现可以将 \(2\times 2\) 的矩阵取出最大值放到某一个位置(见程序)

于是冒泡排序即可。

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
const int dx[] = {0, 1, 1, 0}, dy[] = {0, 0, 1, 1};
int n;
void out(int x1, int y1, int x2, int y2, int x3, int y3){printf("%c%d > %c%d ? %c%d\n", y1+'a'-1, x1, y2+'a'-1, x2, y3+'a'-1, x3);}
void work(int x, int y, int c){for(Rint i = c+1;i < c+4;++ i) out(x+dx[i&3], y+dy[i&3], x+dx[c], y+dy[c], x, y);}
int main(){
    scanf("%d", &n);
    for(Rint i = n;i > 2;-- i){
        for(Rint j = n;j;-- j){
            for(Rint x = 1;x <= i-2;++ x)
                for(Rint y = n-1;y;-- y) work(x, y, 1);
            for(Rint x = 1;x <= j-1;++ x) work(i-1, x, 2);
        }
        out(i, 2, i, 1, i-1, 1); out(i-1, 2, i, 2, i-1, 1); out(i-1, 1, i-1, 2, i-1, 1);
        work(i-2, 1, 1); out(i, 1, i-1, 1, i-1, 1);
    }
}

Moscow Pre-Finals Workshop 2019. KAIST Contest

A A Plus Equals B

题目描述:给定两个正整数 \(a,b\),你可以进行不超过 \(5000\) 次操作,形如 (a|b)+=(a|b),使得 \(a=b\)

数据范围:\(a,b\le 10^{18}\)

容易发现若 \(a,b\) 中至少有一个偶数,则可以 1 步操作直接将其 /2,否则不妨设 \(a<b\),则执行操作 b+=a,a+=a,可以变为 \((a,\frac{a+b}2)\),此时 \(|a-b|\) 减半,至多做 \(\log_2|a-b|\) 次操作变为前者的情况。

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const char *str[] = {"A+=A", "B+=B", "A+=B", "B+=A"};
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
LL a, b; vector<int> ans;
int main(){
	read(a); read(b);
	while(a ^ b){
		while(!(a & 1) && !(b & 1)){a >>= 1; b >>= 1;}
		while(!(a & 1)){a >>= 1; ans.PB(1);}
		while(!(b & 1)){b >>= 1; ans.PB(0);}
		if(a < b){ans.PB(3); ans.PB(0); b = a + b >> 1;}
		else if(a > b){ans.PB(2); ans.PB(1); a = a + b >> 1;}
	}
	printf("%d\n", ans.size());
	for(Rint u : ans) puts(str[u]);
}

F Fruit Tree

题目描述:【总统选举】搬到了树上iee

I Increasing Sequence

题目描述:给定长为 \(n\) 的排列 \(p_i\),对于所有 \(i\in[1,n]\cap\Z\),求有多少个 \(j\ne i\) 使得删去 \(p_j\) 之后经过 \(p_i\) 的最长上升子序列长度降低。

数据范围:\(n\le 2.5\times 10^5\)

看上去就像我不会的套路题

容易发现 \(j<i\)\(j>i\) 的情况互相独立,于是可以分开做。仅考虑 \(j<i\) 的情况,另一种同理。

\(pre_i\) 表示 \(j_\max\),则以 \(pre_i\)\(i\) 的父亲建树,\(dep_i-1\) 就是答案。

如何求 \(pre_i\) 呢,设 \(dp_i\) 表示以 \(i\) 结尾的最长上升子序列长度,则 \(pre_i=\text{LCA}\{j|j<i,a_j<a_i,dp_j+1=dp_i\}\),由于相同 \(dp_i\)\(i\) 满足 \(p_i\) 递减,于是由归纳法得,设 \(l_i,r_i\) 为这个集合的最值,则 \(pre_i=\text{LCA}(l_i,r_i)\)

这里需要边求 \(\text{LCA}\) 边加叶子,只能使用倍增法。时空复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 250003;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
int n, a[N], ans[2][N], val[N], len, p[18][N], dep[N];
vector<int> vec[N];
int lca(int u, int v){
	if(dep[u] < dep[v]) swap(u, v);
	for(Rint i = 17;~i;-- i) if(dep[u] - dep[v] >= (1 << i)) u = p[i][u];
	if(u == v) return u;
	for(Rint i = 17;~i;-- i) if(dep[u] >= (1 << i) && p[i][u] != p[i][v]){u = p[i][u]; v = p[i][v];}
	return p[0][u];
}
void solve(int *ans){
	for(Rint i = 0;i < n;++ i) vec[i].clear(); dep[n] = len = 0;
	for(Rint i = 0;i < n;++ i){
		int p = lower_bound(val, val + len, a[i]) - val;
		val[p] = a[i]; vec[p].PB(i); if(p == len) ++ len;
	}
	for(Rint u : vec[0]){p[0][u] = n; dep[u] = 1;}
	for(Rint i = 1;i < len;++ i){
		int l = 0, r = 0, tmp = vec[i-1].size();
		for(Rint u : vec[i]){
			while(a[vec[i-1][l]] > a[u]) ++ l;
			while(r < tmp && vec[i-1][r] < u) ++ r;
			dep[u] = dep[p[0][u] = lca(vec[i-1][l], vec[i-1][r-1])] + 1;
			for(Rint j = 1;(1<<j) <= dep[u];++ j) p[j][u] = p[j-1][p[j-1][u]];
		}
	}
	for(Rint i = 0;i < n;++ i) ans[i] = dep[i] - 1;
}
int main(){
	read(n);
	for(Rint i = 0;i < n;++ i){read(a[i]); --a[i];}
	solve(ans[0]); reverse(a, a + n);
	for(Rint i = 0;i < n;++ i) a[i] = n - a[i] - 1;
	solve(ans[1]);
	for(Rint i = 0;i < n;++ i) printf("%d ", ans[0][i] + ans[1][n-i-1]);
}

C Cactus Determinant

题目描述:给定 \(n\) 个点 \(m\) 条边的边-仙人掌,求邻接矩阵行列式\(\bmod 993244853\)

数据范围:\(n\le 5\times 10^4\)

关于行列式的符号,实际上就是 \(n\) 减去 \(i\rightarrow p_i\) 所形成的环个数。

枚举这个图的子图,满足每个连通块是一个匹配或一个简单环,贡献就是 \(2^{环个数}(-1)^{n-总数}\)

直接建出圆方树然后做树形 dp 即可虽然我是第一次写。时空复杂度 \(O(n+m)\)

关于圆点和方点贡献的问题,实际上圆方树等价于将环从一个点(父亲)上劈开,总体贡献要记到父亲上去,要稍微改一改转移方程。

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 100003, mod = 993244853;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int n, m, head[N], to[N<<1], nxt[N<<1], dp[N][2], dep[N], ctot, fa[N]; bool vis[N];
vector<int> E[N], cyc; 
void add(int a, int b){
	static int cnt = 0;
	to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt;
}
void dfs(int x){
	vis[x] = fa[x];
	for(Rint i = head[x];i;i = nxt[i]) if(to[i] != fa[x]){
		if(!dep[to[i]]){dep[to[i]] = dep[x] + 1; fa[to[i]] = x; dfs(to[i]);}
		else if(dep[to[i]] < dep[x]){
			++ ctot; int tmp = x;
			while(tmp != to[i]){
				E[tmp].PB(ctot); E[ctot].PB(tmp);
				vis[tmp] = false; tmp = fa[tmp];
			}
			E[tmp].PB(ctot); E[ctot].PB(tmp);
		}
	} if(vis[x]){E[x].PB(fa[x]); E[fa[x]].PB(x);}
}
void dfs2(int x, int f = 0){
	for(Rint v : E[x]) if(v != f) dfs2(v, x);
	if(x <= n){
		dp[x][0] = 1;
		for(Rint v : E[x]) if(v != f){
			dp[x][1] = ((LL) dp[x][1] * dp[v][1] + (LL) dp[x][0] * dp[v][0]) % mod;
			dp[x][0] = (LL) dp[x][0] * dp[v][1] % mod;
		}
		if(dp[x][1]) dp[x][1] = mod - dp[x][1];
	} else {
		int pos = find(E[x].begin(), E[x].end(), f) - E[x].begin(); cyc.clear();
		for(Rint i = pos+1;i < E[x].size();++ i) cyc.PB(E[x][i]);
		for(Rint i = 0;i < pos;++ i) cyc.PB(E[x][i]);
		int len = cyc.size(), t[2][2] = {{0, 1}, {0, 0}};
		for(Rint v : cyc){
			t[1][0] = (LL) t[0][1] * dp[v][0] % mod;
			t[1][1] = ((LL) t[0][1] * dp[v][1] + (LL) (mod - t[0][0]) * dp[v][0]) % mod;
			t[0][0] = t[1][0]; t[0][1] = t[1][1];
		}
		dp[x][0] = t[0][0]; dp[x][1] = t[0][1];
		t[0][0] = 0; t[0][1] = dp[cyc[0]][0];
		for(Rint i = 1;i < len;++ i){
			t[1][0] = (LL) t[0][1] * dp[cyc[i]][0] % mod;
			t[1][1] = ((LL) t[0][1] * dp[cyc[i]][1] + (LL) (mod - t[0][0]) * dp[cyc[i]][0]) % mod;
			t[0][0] = t[1][0]; t[0][1] = t[1][1];
		}
		qmo(dp[x][0] += t[0][1] - mod); int tmp = (len & 1) ? 2 : mod-2;
		for(Rint v : cyc) tmp = (LL) tmp * dp[v][0] % mod;
		qmo(dp[x][0] += tmp - mod);
	}
}
int main(){
	read(n); read(m);
	for(Rint i = 1, u, v;i <= m;++ i){
		read(u); read(v);
		add(u, v); add(v, u);
	}
	ctot = n; dep[1] = 1; dfs(1); dfs2(1); printf("%d\n", dp[1][1]);
}

H Hard to Explain

题目描述:给定一棵以 \(1\) 为根的有 \(n\) 个点的树,每个点上有三个正整数权值 \(a_i,b_i,c_i\),有 \(q\) 次询问,每次询问给出点 \(v\) 和自然数 \(t\),求 \(1\)\(v\) 的路径上,所有 \(c_i\ge t\) 的节点中 \(b_it+a_i\) 的最小值。

数据范围:\(n\le 8\times 10^4,q\le 1.6\times 10^5,a_i,b_i,c_i,t\le 10^9,c_1=10^9,b_{fa_x}\le b_x(x\ne 1),v\le n\)

直接上可持久化李超树,然后复杂度 \(O((n+q)\log n)\) 就做完了?斜率的单调性有用么...

G Good Set

题目描述:给定正整数 \(k\),定义全集 \(U=[0,2^k)\cap\N\),给定集合 \(S\subseteq U\),求有多少个集合 \(A\) 满足 \(S\subseteq A\subseteq U\),且 \(A\) 对于 \(|\)\(\&\) 运算封闭。

数据范围:\(k\le 7\)

定义 \(U\) 的子集族 \(\mathcal{F}\) 表示所有对于 \(|\)\(\&\) 封闭的,包含 \(0\)\(2^k-1\) 的子集组成的集合。

定义有向图 \(G=(V,E)\) 满足传递性当且仅当 \((u,v),(v,w)\in E\Rightarrow (u,w)\in E\)

下面证明:\(\mathcal F\)\(|V|=k\) 的满足传递性的有向图可以一一对应。

对于一个子集 \(T\in\mathcal F\),定义 \(f(T)=(V,E)\),其中 \(|V|=k\),且 \((u,v)\in E\Leftrightarrow(\forall A\in T,u\in A\Rightarrow v\in A)\)。显然 \(f(T)\) 具有传递性。

对于 \(k\) 个点的满足传递性的有向图 \(G=(V,E)\),定义 \(g(G)\subseteq U\),其中 \(A\in g(G)\Leftrightarrow(\forall (u,v)\in E,u\in A\Rightarrow v\in A)\)。易得 \(g(G)\in\mathcal F\)

不易得感性理解一下 \(f(g(G))=G,g(f(T))=T\),于是就证明了 \(f,g\) 互为逆映射,证毕。

问题就可以转化为其对应的有向图数量。先枚举 \(A\) 中的最小集 \(S\) 和最大集 \(T\),显然 \(\forall X\in A,S\subseteq X\subseteq T\),只需考虑 \(T-S\) 的部分即可。dfs 枚举边并通过 bitmask 判断即可,可枚举的顺序非常迷,不知为何这样是最快的。

时间复杂度据说是 \(O(k\cdot Ans)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 128;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
int k, n, x[N], r[7], m, minv, maxv, ans, cnt, uy[7], vy[7], un[7], vn[7];
bool e[7][7]; pii a[N];
void dfs(int d){
	if(d == cnt){++ ans; return;}
	int x = a[d].fi, y = a[d].se;
	if(!(uy[x] & vy[y])){
		un[x] |= 1 << y; vn[y] |= 1 << x; dfs(d + 1);
		un[x] ^= 1 << y; vn[y] ^= 1 << x;
	}
	if(!(un[x] & uy[y]) && !(vy[x] & vn[y]) && e[x][y]){
		uy[x] |= 1 << y; vy[y] |= 1 << x; dfs(d + 1);
		uy[x] ^= 1 << y; vy[y] ^= 1 << x;
	}
}
void solve(int S, int T){
	m = cnt = 0;
	for(Rint i = 0;i < k;++ i) if((S ^ T) >> i & 1) r[m++] = i;
	for(Rint a = 0;a < m;++ a)
		for(Rint b = 0;b < m;++ b) e[a][b] = true;
	for(Rint i = 0;i < n;++ i)
		for(Rint a = 0;a < m;++ a) if(x[i] >> r[a] & 1)
			for(Rint b = 0;b < m;++ b) if(!(x[i] >> r[b] & 1)) e[a][b] = false;
	for(Rint i = 0;i < m;++ i)
		for(Rint j = 0;j < i;++ j){
			a[cnt++] = MP(j, i); a[cnt++] = MP(i, j);
		}
	dfs(0);
}
int main(){
	read(k); read(n); minv = (1 << k) - 1;
	for(Rint i = 0;i < n;++ i){read(x[i]); minv &= x[i]; maxv |= x[i];}
	for(Rint S = 0;S < (1 << k);++ S) if((S | maxv) == S){
		for(Rint T = S;T;T = T - 1 & S) if((T & minv) == T) solve(S, T);
		solve(S, 0);
	} printf("%d\n", ans);
}

E Eat Economically

题目描述:给定 \(2n\) 种套餐 \((l,d)\),分别表示这种套餐在午饭和晚饭时的价格。每天要吃午饭和晚饭,每顿饭吃一种套餐,不能吃同一种套餐两次。对于所有 \(i\in[1,n]\cap\Z\),求吃 \(i\) 天花的最少钱数。

数据范围:\(n\le 2.5\times 10^5\)

这东西看上去就挺费用流的实际上就是

\(4\) 个 priority_queue 或 set 维护当前可选的 \(l_i,d_i\) 以及 \(l_i,d_i\) 之间互换的代价,贪心选价值最低的增广路即可。

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 500003;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
int n, a[N], b[N]; LL ans; set<pii> s[4];
int main(){
	read(n); pii tmp = MP(1e9+1, -1);
	for(Rint i = 0;i < 4;++ i) s[i].insert(tmp);
	for(Rint i = 0;i < (n<<1);++ i){read(a[i]); read(b[i]); s[0].insert(MP(a[i], i)); s[1].insert(MP(b[i], i));}
	for(Rint _ = 0;_ < n;++ _){
		int t1 = s[0].begin()->fi, t2 = s[1].begin()->fi + s[3].begin()->fi, u;
		if(t1 <= t2){
			u = s[0].begin()->se; ans += t1;
			s[0].erase(s[0].begin());
			s[1].erase(MP(b[u], u));
		} else {
			u = s[1].begin()->se; ans += t2;
			s[3].insert(MP(a[u] - b[u], u));
			s[1].erase(s[1].begin());
			s[0].erase(MP(a[u], u));
			u = s[3].begin()->se;
			s[3].erase(s[3].begin());
		} s[2].insert(MP(b[u] - a[u], u));
		t1 = s[1].begin()->fi; t2 = s[0].begin()->fi + s[2].begin()->fi;
		if(t1 <= t2){
			u = s[1].begin()->se; ans += t1;
			s[1].erase(s[1].begin());
			s[0].erase(MP(a[u], u));
		} else {
			u = s[0].begin()->se; ans += t2;
			s[2].insert(MP(b[u] - a[u], u));
			s[0].erase(s[0].begin());
			s[1].erase(MP(b[u], u));
			u = s[2].begin()->se;
			s[2].erase(s[2].begin()); 
		} s[3].insert(MP(a[u] - b[u], u)); printf("%lld\n", ans);
	}
}

-D Dijkstra Is Playing At My House

题目描述:给定 \(n\) 个障碍,每个障碍是一个边平行于坐标轴,顶点是整点的矩形 \((a_i,b_i,c_i,d_i)\),你每次可以向 \(4\) 个方向走 \(1\) 单位长度。求从 \((s_x,s_y)\)\((e_x,e_y)\) 的最短路长度。

数据范围:\(n\le 2.5\times 10^5\),值域 \([0,10^8]\cap\N\)

J Jealous Teachers

题目描述:给定 \(n-1\) 名学生和 \(n\) 名老师,每名学生分别准备了 \(n\) 朵花,给定 \(m\) 个二元组 \((s_i,t_i)\) 表示第 \(s_i\) 名学生可以将花给第 \(t_i\) 名老师,求构造使每名老师得到 \(n-1\) 朵花的方案,需判断无解。

数据范围:\(2\le n\le 10^5,1\le m\le 2\times 10^5\)

根据最大流最小割定理,有解充要条件是对于任意 \(k\) 名学生,他们能送花的老师数量 \(>k\)

根据 Hall 定理,必定存在一个 \(n-1\) 名学生与 \(n-1\) 名老师的匹配。用 dinic 把匹配找出来。

从落单的老师开始 dfs,枚举能给他送花的学生匹配的老师,若这个老师没有被访问过则 dfs 下去,形成一棵 \(n\) 个点的树结构,从下往上递推即可。

时间复杂度瓶颈是 dinic。

#include<bits/stdc++.h>
using namespace std;
const int N = 200010, M = N<<2, INF = 0x3f3f3f3f;
template<typename T>
void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, m, S, T, cnt=1, head[N], to[M], nxt[M], cap[M];
void add(int a, int b){
    to[++cnt] = b; nxt[cnt] = head[a]; head[a] = cnt; cap[cnt] = 1;
    to[++cnt] = a; nxt[cnt] = head[b]; head[b] = cnt; cap[cnt] = 0;
}
int q[N], f, r, cur[N], dep[N];
bool bfs(){
    memset(dep, 0x3f, sizeof dep);
    memcpy(cur, head, sizeof cur);
    dep[S] = f = r = 0; q[r++] = S;
    while(f < r){
        int u = q[f++];
        for(int i = head[u];i;i = nxt[i])
            if(cap[i] && chmin(dep[to[i]], dep[u] + 1))
                q[r++] = to[i];
    } return dep[T] < INF;
}
int dfs(int x, int lim){
    if(x == T || !lim) return lim;
    int flow = 0, f;
    for(int &i = cur[x];i;i = nxt[i])
        if(dep[to[i]] == dep[x] + 1 && (f = dfs(to[i], min(lim, cap[i])))){
            flow += f; lim -= f; cap[i] -= f; cap[i^1] += f; if(!lim) break;
        }
    return flow;
}
int res, mat[N]; bool vis[N];
int dfs(int x){
    vis[x] = true; int tmp = 0;
    for(int i = head[x];i;i = nxt[i]) if(to[i] < n){
        int v = to[mat[to[i]]]; if(vis[v]) continue;
        int tt = dfs(v);
        cap[i^1] = tt+1; cap[mat[to[i]]] = n-tt-1;
        tmp += tt+1;
    } return tmp;
}
int main(){
    read(n); read(m); S = n<<1; T = S + 1;
    for(int i = 1;i < n;++ i) add(S, i);
    for(int i = n;i < S;++ i) add(i, T);
    for(int i = 1, u, v;i <= m;++ i){
        read(u); read(v); add(u, v + n-1);
    } while(bfs()) res += dfs(S, INF);
    if(res < n-1){puts("-1"); return 0;}
    for(int i = 1;i < n;++ i)
        for(int j = head[i];j;j = nxt[j])
            if(to[j] < S && !cap[j]){mat[i] = j; break;}
    for(int i = n;i < S;++ i)
        for(int j = head[i];j;j = nxt[j])
            if(to[j] == T && cap[j]){
                memset(cap, 0, sizeof cap);
                if(dfs(i) != n-1) puts("-1");
                else for(int _ = S<<1;_ < cnt;_ += 2)
                    printf("%d\n", cap[_]);
                return 0;
            }
}

2018-2019 9th BSUIR Open Programming Championship. Semifinal

I Equal Mod Segments

题目描述:给定 \(n\) 个正整数 \(a_i\),求有多少个正整数对 \((L,R)\) 满足 \(1\le L\le R\le n\)\(a_L\bmod a_{L+1}\bmod\dots\bmod a_R=a_R\bmod a_{R-1}\bmod\dots\bmod a_L\)

数据范围:\(n\le 10^5,a_i\le 3\times 10^5\)

对于左边的柿子,固定 \(L\) 之后只有 \(\log V\) 种取值,每种取值对应的是 \(R\) 属于一段区间。右边同理。

所以求出这 \(O(n\log V)\) 个区间之后扫描线即可,时间复杂度 \(O(n\log n\log V)\)

-B Varvara and matrix

题目描述:给定矩阵 \(\{a\}_{n\times m}\) 和正整数 \(k,A,B\),定义它的权值为满足 \(1\le x_1<x_2\le n,1\le y_1<y_2\le m\)\(a_{x_1,y_1}=a_{x_1,y_2}=a_{x_2,y_1}=a_{x_2,y_2}\) 的四元组 \((x_1,x_2,y_1,y_2)\) 的数量,构造将所有 \(0\) 变为 \(A\)\(B\) 的方案,使得它的权值不变。

数据范围:\(2\le n,m\le 10^3,2\le k\le nm,1\le A,B\le k,A\ne B,0\le a_{i,j}\le k\)。每行每列至多只有 \(1\)\(0\)

弦图

被迫来学知识点了...

定义:连接环上两个不相邻节点的边称为。一个无向图为弦图当且仅当任意一个大小 \(>3\) 的环都有至少一条弦。无向图的色数是给点染色的最少颜色数量,使得相邻的点不同色。无向图的团数是最大团的大小。无向图的最大独立集是满足其中任意两个点不相邻的最大点集。无向图的最小团覆盖是用最少的团覆盖所有点的方案。无向图中的点 \(v\)单纯点当且仅当与其相邻的点集的诱导子图是一个团。无向图的点集的排列 \(v_1,v_2,\dots,v_n\) 是一个完美消除序列当且仅当 \(v_i\)\(\{v_i,v_{i+1},\dots,v_n\}\) 的诱导子图中是单纯点。

结论:团数\(\le\)色数,|最大独立集|\(\le\)|最小团覆盖|。弦图的诱导子图也是弦图。

引理:弦图有单纯点。不是完全图的弦图有两个不相邻的单纯点。

定理:无向图是弦图的充要条件是有完美消除序列。

最大势算法:贪心,维护每个节点的"势" \(label[x]\) 表示已经加入序列的点中与 \(x\) 相邻的数量。每次将最大势的点加入序列开头并更新与其相邻的点的 \(label[x]\),得到一个消除序列。

判断完美消除序列算法:对于排列 \(v_1,v_2,\dots,v_n\),对于所有 \(i\in[1,n]\cap\N\),与 \(v_i\) 相邻的点为 \(v_{j_1},\dots,v_{j_k}\)\(v_{j_1}\)\(v_{j_2},\dots,v_{j_k}\) 相邻,则 \(v\) 为完美消除序列。复杂度线性。

判断弦图算法:用最大势算法求出一个消除序列,判断它是否完美。复杂度线性。【正确性证明?】

计算团数:设 \(p(v)\)\(v\) 在完美消除序列中的位置,\(N(v)=\{w|w与v相邻且p(w)>p(v)\}\),则弦图的所有极大团形如 \(v\cup N(v)\)

计算最小染色方案:由计算团数的方案可得,按照完美消除序列的逆序贪心,得到颜色数为团数的方案,又因为团数\(\le\)色数,所以即为最小染色方案。

计算最大独立集:按照完美消除序列的顺序贪心。

计算最小团覆盖方案:设 \(\{p_1,\dots,p_t\}\) 为最大独立集,则 \(\{p_1\cup N(p_1),\dots,p_t\cup N(p_t)\}\) 为一种方案。

推论:弦图中,团数\(=\)色数,|最大独立集|=|最小团覆盖|。

区间图

定义:给定 \(n\) 个区间 \((l_i,r_i)\),定义相交图为每个点代表一个区间,两个点相连当且仅当这两个区间相交。无向图是区间图当且仅当存在若干个区间的相交图是它。

定理:区间图是弦图。按右端点排序得到的是完美消除序列。

300iq Contest 3

A Airplane Cliques

题目描述:给定一棵 \(n\) 个点的树和自然数 \(x\),对于 \(k\in[1,n]\cap\N\),求满足其中任意两个点之间的距离 \(\le x\)\(k\) 元点集的数量\(\bmod 998244353\)

数据范围:\(n\le3\times10^5,x<n\)

构造无向图 \(G=(V,E)\),其中 \((u,v)\in E\Leftrightarrow dis(u,v)\le x\)。则 \(G\) 是弦图,以任意点为根的任意 bfs 序是完美删除序列的逆序。枚举这个团在完美删除序列中最靠后的点 \(v\),则这个团是 \(v\cup N(v)\) 的子集。设 \(a_v=|N(v)|\),则答案就是 \(\sum\binom{a_v}{k-1}\),计算它可以使用 FFT 优化卷积。计算 \(a_i\) 据说可以用各种方法而我只会淀粉质,于是复杂度 \(O(n\log^2n)\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
namespace {
const int N = 311111, M = 1<<20, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
void qmo(int &x){x += (x >> 31) & mod;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
	return res;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
}
int n, d, tot, rt, siz[N], dep[N], p[N], num[N], ans[N];
vector<int> E[N];
void dfs(int x, int f = 0){for(Rint v : E[x]) if(v != f){dep[v] = dep[x] + 1; dfs(v, x);}}
bool cmp(int a, int b){return dep[a] < dep[b];}
int tr[N], cnt;
void upd(int p, int v){for(;p <= n;p += p & -p) tr[p] += v;}
int qry(int p){int r = 0; for(;p;p -= p & -p) r += tr[p]; return r;}
pii tmp[N];
bool vis[N];
void getrt(int x, int f = 0){
	siz[x] = 1; bool t = true;
	for(Rint v : E[x]) if(v != f && !vis[v]){getrt(v, x); if((siz[v]<<1)>tot) t = false; siz[x] += siz[v];}
	if(t && tot<=(siz[x]<<1)) rt = x;
}
void dfs2(int x, int dep = 0, int f = 0){
	tmp[cnt++] = MP(dep, x);
	for(Rint v : E[x]) if(v != f && !vis[v]) dfs2(v, dep+1, x);
}
void calc(int x, int op, int lim){
	int r = 0; cnt = 0; dfs2(x); sort(tmp, tmp + cnt);
	for(Rint l = cnt-1;~l;-- l){
		while(r < cnt && tmp[l].fi + tmp[r].fi <= lim) upd(num[tmp[r++].se], 1);
		ans[tmp[l].se] += op * qry(num[tmp[l].se]-1);
	}
	for(Rint i = 0;i < r;++ i) upd(num[tmp[i].se], -1);
}
void solve(int x){
	vis[x] = true; calc(x, 1, d);
	for(Rint v : E[x]) if(!vis[v]) calc(v, -1, d-2);
	for(Rint v : E[x]) if(!vis[v]){tot = siz[v]; getrt(rt = v, x); solve(rt);}
}
int fac[N], inv[N], A[M], B[M], lim, rev[M], w[2][M];
void init(int n){
	fac[0] = 1;
	for(Rint i = 1;i <= n;++ i) fac[i] = (LL) fac[i-1] * i % mod;
	inv[n] = ksm(fac[n], mod - 2);
	for(Rint i = n;i;-- i) inv[i-1] = (LL) inv[i] * i % mod;
}
void calrev(int len){
	int L = -1; lim = 1;
	while(lim <= len){lim <<= 1; ++ L;}
	for(Rint i = 0;i < lim;++ i) rev[i] = (rev[i>>1]>>1) | ((i&1)<<L);
	for(Rint mid = 1;mid < lim;mid <<= 1){
		w[0][mid] = w[1][mid] = 1; int Wn = ksm(3, (mod - 1) / (mid << 1));
		for(Rint i = 1;i < mid;++ i) w[0][mid+i] = (LL) w[0][mid+i-1] * Wn % mod;
		for(Rint i = 1;i < mid;++ i) w[1][mid+i] = mod - w[0][(mid<<1)-i];
	}
}
void NTT(int *A, int op){
	for(Rint i = 0;i < lim;++ i) if(i < rev[i]) swap(A[i], A[rev[i]]);
	for(Rint mid = 1;mid < lim;mid <<= 1)
		for(Rint i = 0;i < lim;i += mid<<1)
			for(Rint j = 0;j < mid;++ j){
				int y = (LL) A[mid + i + j] * w[op][mid + j] % mod;
				qmo(A[mid + i + j] = A[i + j] - y); qmo(A[i + j] += y - mod);
			}
	if(op){
		int inv = ksm(lim, mod - 2);
		for(Rint i = 0;i < lim;++ i) A[i] = (LL) A[i] * inv % mod;
	}
}
int main(){
	read(n); read(d);
	for(Rint i = 1, u, v;i < n;++ i){
		read(u); read(v); E[u].PB(v); E[v].PB(u);
	} dfs(1);
	for(Rint i = 1;i <= n;++ i) p[i] = i;
	sort(p + 1, p + n + 1, cmp);
	for(Rint i = 1;i <= n;++ i) num[p[i]] = i;
	tot = n; getrt(1); solve(rt); init(n); calrev(n-1<<1);
	for(Rint i = 1;i <= n;++ i) ++ A[ans[i]];
	for(Rint i = 0;i < n;++ i) A[i] = (LL) A[i] * fac[i] % mod;
	for(Rint i = 0;i < n;++ i) B[i] = inv[n-1-i];
	NTT(A, 0); NTT(B, 0);
	for(Rint i = 0;i < lim;++ i) A[i] = (LL) A[i] * B[i] % mod;
	NTT(A, 1);
	for(Rint i = 0;i < n;++ i) printf("%lld ", (LL) A[i+n-1] * inv[i] % mod); 
}

B Best Tree

题目描述:给定长为 \(n\) 的正整数序列 \(d_i\),求在所有 \(n\) 个点的,第 \(i\) 个节点的度数为 \(d_i\) 的树中,最大匹配个数的最大值。\(t\) 组数据。

数据范围:\(n\ge 2,\sum n\le 2\times 10^5\)

众所周知,树上最大匹配的做法就是从叶子往上贪心,所以每次选取一个 \(d_u>1\) 的点,然后删去 \(d_u-1\) 个叶子(易得必定可行),匹配个数+1。于是当 \(n=2\) 时答案为 \(1\),否则答案为 \(\min(\sum[d_i>1],\frac{n}{2})\)

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
#define PB push_back
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 200003, mod = 998244353;
template<typename T>
void read(T &x){
	int ch = getchar(); x = 0; bool f = false;
	for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	if(f) x = -x;
}
int t, n, s, x;
int main(){
	read(t);
	while(t --){
		read(n); s = 0;
		for(Rint i = 0;i < n;++ i){read(x); s += x > 1;}
		if(n == 2) puts("1");
		else printf("%d\n", min(s, n>>1));
	}
}

C Cells Blocking

题目描述:给定 \(n\times m\) 的矩阵,每个元素是障碍或空地,求有多少种将其中两个空地变为障碍的方案,使得你无法从 \((1,1)\) 开始,每次向下或向右走一格,到达 \((n,m)\)

数据范围:\(n,m\le 3000\)

首先特判掉本来就不可达的情况。

这种 \(2/4\) 方向的路径联通性问题,一般可以考虑极端路径。考虑其中的两条路径:能向下则向下 和 能向右则向右。如果 block 掉这两条路径的交点,那么一定不可达,否则这两条路径必须各 block 一格,枚举其中一格更新路径交点即可。时间复杂度 \(O((n+m)^2)\)

D Disjoint LIS

本题是提交答案题(?)

题目描述:对于所有正整数 \(n\le 75\),求可以选出两个不交的 LIS 的长为 \(n\) 的排列的数量\(\bmod 998244353\)

基础样表理论练习题。一个排列与两个相同规模的标准样表一一对应。题目条件即为样表的前两行一样长。暴力枚举整数拆分即可。时间复杂度 \(O(np(n))\),其中 \(p(n)\)\(n\) 的整数拆分方案数。

#include<cstdio>
using namespace std;
const int ans[] = {0, 1, 1, 5, 26, 132, 834, 6477, 56242, 532712, 5561524, 64098432, 806184757, 948233294, 989748344, 143602156, 634648323, 421103776, 925864750, 797907052, 912638617, 772780014, 386111399, 327774735, 845702948, 791619521, 878631164, 37336541, 165629590, 969299909, 774782650, 860045241, 866378903, 492112739, 824431980, 949969944, 970248926, 278772092, 118910029, 190687907, 976616702, 231648679, 722078798, 677622359, 166145100, 322790432, 114852733, 809056236, 133146712, 978647108, 363079147, 447411358, 261833982, 586593658, 543018772, 281510931, 509498959, 499059113, 216456979, 892597175, 288388296, 782889761, 259994934, 668319048, 447568639, 974154792, 434978721, 809640594, 541844549, 9840250, 275431656, 31040948, 964592956, 743394989, 89362287};
int main(){int n; scanf("%d", &n); printf("%d\n", ans[n-1]);}

E Easy Win

题目描述:给定长为 \(n\) 的正整数序列 \(a_i\),对于所有 \(x\in[2,n+1]\cap\Z\),求 \(\bigoplus\limits_{i=1}^n(a_i\bmod x)\) 是否为 \(0\)

数据范围:\(a_i\le n\le 5\times 10^5\)

对于枚举 \(x\)\(\lfloor\frac{a_i}x\rfloor\),再分别考虑每一位 \(c\),计算可以使用前缀和优化,时间复杂度 \(O(n\log^2n)\)

H Horrible Cycles

题目描述:给定 \(2\times n\) 个点的二分图,左边第 \(i\) 个点连向右边前 \(a_i\) 个点。求简单环的数量\(\bmod 998244353\)

数据范围:\(n\le 5000,1\le a_i\le n\)

这种 dp 见过好多次但还是忘了,就是维护连通性的时候,以点和连通性为状态转移。

将左边的点按照 \(a_i\) 排序,则右边 \((a_{i-1},a_i]\) 连向左边 \([i,n]\)。更新顺序是 \(i=1\rightarrow n\),加入右边 \((a_{i-1},a_i]\) 再加入左边第 \(i\) 个点,\(F_{?,j}\) 表示当前加入 \(?\) 个点,右边的点形成 \(j\) 个连续段。

300iq Contest 1

A Angle Beats

题目描述:给定 \(n\times m\) 的表格,每个元素可能是 +*. 中的一种。你可以摆放一些大小为 \(3\) 的块在上面:将 \(1\times 3\) 矩形的中心放在 + 上,或将 L 形中心放在 *+ 上。这些块不能相交,求最多能放多少个块,并构造方案。

数据范围:\(2\le n,m\le 100\)

一看就是个二分图最大匹配,可如何建图呢?

若只有 +,则可以将每个 + 拆成两个点,分别都向四周连边,答案就是最大匹配减去 + 的个数。

考虑上 * 之后,* 就可以改为两个点分别向竖直、水平方向连边。智慧建图

B Best Subsequence

题目描述:给定长为 \(n\) 的正整数序列 \(a\),求

\[\min\{\max\{a_{i_j}+a_{i_{(j+1)\text{mod} k}}|j\in[0,k)\}|1\le i_0<\dots<i_{k-1}\le n\} \]

数据范围:\(3\le k\le n\le 2\times 10^5,a_i\le 10^9\)

不会贪心?啥也不会

每次删掉最大的 \(a_{i_j}+a_{i_{j+1}}\) 中较大的数。时间复杂度 \(O(n\log n)\)

C Cool Pairs

题目描述:给定两个长为 \(n\) 的排列 \(p,q\) 和自然数 \(k\),构造两个长为 \(n\) 的正整数序列 \(a,b\) 满足:

  • \(-n\le a_i,b_i\le n\)
  • \(a_{p_1}\le a_{p_2}\le\dots\le a_{p_n}\)\(b_{q_1}\le b_{q_2}\le\dots\le b_{q_n}\)
  • \(|\{(i,j)|i<j\and a_i<b_j\}|=k\)

数据范围:\(n\le 3\times 10^5,k\le\binom n2\)

这是一个构造顺序对的过程,用 BIT 维护桶即可,时间复杂度 \(O(n\log n)\)

E Expected Value

题目描述:随机游走的步数期望。

数据范围:\(n\le 3000\),平面图。

步数 \(>i\) 的概率是长度 \(\le n\) 的递推数列,BM 即可。时间复杂度 \(O(nm)\)

Tutte Theorem

无向图 \(G=(V,E)\) 有完美匹配 \(\Leftrightarrow\forall U\subseteq V,o(V-U)\le|U|\)\(o(X)\) 表示子图 \(X\) 的奇连通块数)

证明:不会

Tutte-Berge formula

无向图 \(G=(V,E)\) 的最大匹配数为 \(\frac{1}{2}\min\limits_{U\subseteq V}\{|U|+|V|-o(V-U)\}\)

G Graph Counting

题目描述:给定正整数 \(n\),求没有完美匹配,但任意加上一条边之后就会有完美匹配的 \(2n\) 个点的无标号无向图数量\(\bmod 998244353\)

数据范围:\(n\le5\times10^5\)

根据 Tutte-Berge formula,题意条件可以等价为 \(\min_{U\subseteq V}\{|U|-o(V-U)\}=-2\),且对于所有这样的点集 \(U\),内部所有点的度数都是 \(2n-1\),外部都是大小为奇数的联通块。可得答案就是 \(p(n+1)-1\),其中 \(p(n)\) 表示 \(n\) 的整数拆分方案数,使用五边形数定理和多项式求逆,时间复杂度 \(O(n\log n)\)

I Interesting Graph

题目描述:给定 \(n\) 个点 \(m\) 条边的无向图,对于所有 \(k\in[1,n]\cap\N\),求 \(k\) 种颜色的染色方案。

数据范围:\(n,m\le 10^5\),点双连通分量大小 \(\le 7\)

看见这个条件就知道,先预处理大小为 \(s\) 的点双连通分量在固定根时的方案数,是关于 \(k\)\(s-1\) 次多项式。将这些多项式乘起来再乘上 \(k\) 的连通块个数次方,多点求值即可得到答案。

计算每个点双连通分量时,可以用暴力枚举+插值。

J Jealous Split

题目描述:给定长为 \(n\) 的自然数序列 \(a\) 和正整数 \(k\),你需要将其分割为 \(k\) 段,使得相邻两段的和之差 \(\le\) 这两段的最大值。需判断无解。

数据范围:\(3\le k\le n\le 10^5\)

-K Knowledge

题目描述:给定字符串 \(S\) 和自然数 \(x\),你可以对其做如下操作:加入/删除子串aa/bbb/ababab。求能得到多少个长为 \(x\) 的字符串。

数据范围:\(|S|\le 3\times 10^5,x\le 10^9,\Sigma=\{a,b\}\)

发现 \(bb\rightarrow abababbb\rightarrow ababa\)\(aa\rightarrow\varnothing\)\(ababab\rightarrow\varnothing\),于是我们一定能将任意一个字符串通过上述操作变为 \(12\) 种串(\(ababa\) 的所有前缀以及 \(b\) 与它们的拼接)。

-CF1408I Bitwise Magic

题目描述:给定长为 \(n\) 的元素互不相同的正整数序列 \(a\) 和正整数 \(k,c\),需要对这个序列操作 \(k\) 次,每次随机选择一个 \(a_i\),将其 \(-1\)。对于所有 \(x\in[0,2^c)\) 输出最后序列的异或和为 \(x\) 的概率\(\bmod 998244353\)

数据范围:\(k,c\le 16,a_i\in [k,2^c)\)

为何能观察到性质:\(|\{(x\oplus(x-1),x\oplus(x-2),\dots,x\oplus(x-k))|x<2^c\}|=O(ck)\),取极限数据时为 \(192\)

证明:这个序列只与 \(x\) 从最低 \(1\) 开始的最后 \(O(\log k)\) 位有关。

\(t=\bigoplus\limits_{i=1}^na_i\),考虑答案在 \(t\) 上的改变量。

使用 EGF 统计操作序列的数量,设 \(d_{i,j}=a_i\oplus(a_i-j)\),要求的就是

\[k![y^k]\prod_{i=1}^n(1+\sum_{j=0}^k\frac{x^{d_{i,j}}y^j}{j!}) \]

其中 \(x\) 是异或卷积,\(y\) 是加法卷积。

容易发现上面那个序列相同时后面的这个多项式也相同,可以合并,然后暴力 Ln,Exp 计算多项式幂。时间复杂度 \(O(2^cck^3)\)一看就过不去

然后发现只需要考虑按 \(x\) 做 FWT 之后每一位的多项式,把相同的再次合并,然后就降低了 Ln,Exp 的次数,时间复杂度不会分析

#include<bits/stdc++.h>
#define PB emplace_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef vector<int> vi;
const int N = (1<<16)+5, mod = 998244353;
void qmo(int &x){x += x >> 31 & mod;}
template<typename T>
void read(T &x){
    int ch = getchar(); x = 0;
    for(;ch < '0' || ch > '9';ch = getchar());
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
int ksm(int a, int b){
    int res = 1;
    for(;b;b >>= 1, a = (LL)a * a % mod)
        if(b & 1) res = (LL)res * a % mod;
    return res;
}
int n, k, c, lim, vcnt, a[N], fac[N], inv[N], iv[N], xsum, sum[192], cnt[N<<1], f[N];
vector<int> vc[192];
map<vi, int> mp;
int res[20], tmp1[20], tmp2[20];
void init(int m){
    fac[0] = 1;
    for(int i = 1;i <= m;++ i) fac[i] = (LL)fac[i-1] * i % mod;
    inv[m] = ksm(fac[m], mod-2);
    for(int i = m;i;-- i){
        inv[i-1] = (LL)inv[i] * i % mod;
        iv[i] = (LL)inv[i] * fac[i-1] % mod;
    }
}
void polyLn(int *f, int *g, int n){
    g[0] = 0;
    for(int i = 1;i <= n;++ i){
        g[i] = 0;
        for(int j = 1;j < i;++ j)
            g[i] = (g[i] + (LL)f[j] * g[i-j]) % mod;
        g[i] = ((LL)i * f[i] + mod - g[i]) % mod;
    } for(int i = 1;i <= n;++ i) g[i] = (LL)g[i] * iv[i] % mod;
}
void polyExp(int *f, int *g, int n){
    g[0] = 1;
    for(int i = 1;i <= n;++ i){
        g[i] = 0; f[i] = (LL)f[i] * i % mod;
        for(int j = 1;j <= i;++ j)
            g[i] = (g[i] + (LL)f[j] * g[i-j]) % mod;
        g[i] = (LL)g[i] * iv[i] % mod;
    }
}
int main(){
    read(n); read(k); read(c); init(N-1); lim = 1<<c;
    for(int i = 1;i <= n;++ i){
        vector<int> tmp(k+1); read(a[i]); xsum ^= a[i];
        for(int j = 0;j <= k;++ j) tmp[j] = a[i] ^ (a[i] - j);
        ++ mp[tmp];
    } for(auto _ : mp){vc[vcnt] = _.fi; sum[vcnt] = _.se; ++ vcnt;}
    for(int S = 0;S < lim;++ S){
        vector<int> val;
        for(int i = 0;i < vcnt;++ i){
            int mask = 0;
            for(int j = 0;j <= k;++ j) mask |= (__builtin_popcount(S & vc[i][j]) & 1) << j;
            if(!cnt[mask]) val.PB(mask); cnt[mask] += sum[i];
        } memset(res, 0, sizeof res); res[0] = 1;
        for(int T : val){
            for(int i = 0;i <= k;++ i)
                tmp1[i] = (T>>i&1) ? mod-inv[i] : inv[i];
            polyLn(tmp1, tmp2, k);
            for(int i = 1;i <= k;++ i) tmp2[i] = (LL)tmp2[i] * cnt[T] % mod;
            polyExp(tmp2, tmp1, k);
            memset(tmp2, 0, sizeof tmp2);
            for(int i = 0;i <= k;++ i)
                for(int j = 0;j <= i;++ j)
                    tmp2[i] = (tmp2[i] + (LL)res[j] * tmp1[i-j]) % mod;
            memcpy(res, tmp2, sizeof res); cnt[T] = 0;
        } f[S] = res[k];
    }
    for(int mid = 1;mid < lim;mid <<= 1)
        for(int i = 0;i < lim;i += mid<<1)
            for(int j = 0;j < mid;++ j){
                int y = f[i+j+mid];
                qmo(f[i+j+mid] = f[i+j] - y);
                qmo(f[i+j] += y - mod);
            }
    int coe = (LL)fac[k] * ksm(lim, mod-2) % mod * ksm(n, mod-1-k) % mod;
    for(int i = 0;i < lim;++ i) printf("%lld%c", (LL)coe * f[i^xsum] % mod, " \n"[i==lim-1]);
}

CF1148H Holy Diver

题目描述:给定一个初始为空的数组,每次操作给定 \(a,l,r,k\),在数组末尾插入 \(a\),再求 \(|\{(i,j)|l\le i\le j\le r\and\text{mex}(a[i:j])=k\}|\)。强制在线。

数据范围:\(n\le 2\times 10^5\)

LOJ2553「CTSC2018」暴力写挂

题目描述:给定两棵 \(n\) 个点的以 \(1\) 为根的树 \(T_1,T_2\),设 \(dep_x,dep'_x\) 分别表示 \(x\)\(T_1,T_2\) 中的深度,\(lca(x,y),lca'(x,y)\) 分别表示 \(x,y\)\(T_1,T_2\) 中的 LCA。求

\[\max\{dep_x+dep_y-dep_{lca(x,y)}-dep'_{lca'(x,y)}|1\le x\le y\le n\} \]

数据范围:\(n\le 366666\)

之前一直都没真正学过边分治,现在来搞一搞。

首先枚举 \(T_2\) 中的 \(lca\),变为了在第二棵树的子树中选出第一棵树中 \(dep_x+dep_y-dep[lca(x,y)]=\frac{1}{2}(dep_x+dep_y+dis_{x,y})\) 的最小值。

\(lca\) 消掉是爲了將 \(dis\) 從分治中心割開,對每個節點維護到邊分樹上祖先的距離,使用類似線段樹合並的方法計算 \((x,y)\) 貢獻並滿足 \(T_2\) 中的 \(lca\) 的限制。時間復雜度 \(O(n\log n)\)

LOJ2261「CTSC2017」密钥

\(A\) 看作 \(1\)\(B\) 看作 \(-1\)。求出前缀和,找到第 \(S\) 大值即可(若有相同的先考虑后面的)。

(所以结论怎么证aaa)

#include<bits/stdc++.h>
using namespace std;
const int N = 2e7+5;
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int n, k, S, tmp, i, mx, p[N], q[N], c[N<<1];
short seed;
int getrand(){return seed = ((seed * 12321) ^ 9999) & 32767;}
int solve(int x){
    int o = mx;
    for(;x >= c[o];x -= c[o--]);
    for(i = n;i;-- i) if(!p[i] && q[i] == o && !x--) return i;
}
int main(){
    scanf("%d%hd%d", &k, &seed, &S); n = k << 1 | 1;
    for(i = 1;i <= n;++ i) tmp += p[i] = getrand() >> 7 & 1; i = 1;
    while(tmp > k){while(!p[i]) ++ i; p[i] = 0; -- tmp;}
    while(tmp < k){while(p[i]) ++ i; p[i] = 1; ++ tmp;}
    q[0] = n;
    for(i = 1;i <= n;++ i){
        q[i] = q[i-1] + 1;
        if(!p[i]){++ c[q[i] -= 2]; chmax(mx, q[i]);}
    }
    printf("%d\n%d\n%d\n", solve(0), solve(S), solve(k - S));
}

LOJ2262「CTSC2017」网络

LOJ2263「CTSC2017」游戏

\[P(A|B)=\frac{P(A)P(B|A)}{P(B)} \]

求每局游戏小 R 获胜的概率之和。对于一个未知局面,设第 \(i\) 局左边第一个已知局面为 \(l\),右边第一个已知局面为 \(r\)。设事件 \(L,R\)\(l,r\) 获胜者与已知相符。

\[\begin{align} P(X|L,R)&=\frac{P(L,R|X)P(X)}{P(L,R)} \\ &=\frac{P(L|X)P(R|X)P(X)}{P(L,R)} \\ &=\frac{P(X|L)P(R|X)}{P(R|L)} \end{align} \]

分子和分母都可以使用线段树维护矩阵连乘积来计算,时间复杂度 \(O((n+m)\log n)\)

posted @ 2020-10-22 20:06  mizu164  阅读(1107)  评论(0编辑  收藏  举报