OpenCup 乱做

OpenCup 牛啊,下文默认 Div.1。

Ancient Magic Circle in Teyvat

Source: XXII Open Cup, GP of Nanjing K
给定一个 \(n\) 个点的完全图,有 \(m\) 条边是红色的,其余边是蓝色的,求出边均为蓝色的大小为 \(4\) 的完全子图个数与边均为红色的大小为 \(4\) 的完全子图个数的差。
\(4\le n\le 10^5\)\(0\le m\le \min(\frac{n(n-1)}{2},2\times 10^5)\)

Solution

考虑容斥,首先对 \(n\) 个点的完全图进行一个号的标:

\(U\) 为所有的四元组,\(A_i\) 表示第 \(i\) 条边为红的四元组个数,考虑容斥原理:

\[|\bigcap^6_{i=1}\overline{A_i}|=|U|+\sum^6_{k=1}(-1)^k\sum_{1\le i_1<i_2<\cdots<i_k\le 6}|A_{i_1}\cap A_{i_2}\cap\cdots\cap A_{i_k}| \]

发现当 \(k\) 等于 \(6\) 时,可以单独划分出来,所以式子可以变成:

\[|\bigcap^6_{i=1}\overline{A_i}|-|\bigcap^6_{i=1}A_i|=|U|+\sum^5_{k=1}(-1)^k\sum_{1\le i_1<i_2<\cdots<i_k\le 6}|A_{i_1}\cap A_{i_2}\cap\cdots\cap A_{i_k}| \]

左边式子的绝对值就是我们要求的,那么考虑求右边。
右边的所有式子可以分开来变成若干种不同的计数对象,如下(图是盗出题人的),其中红边表示必须是红,蓝边表示可以是红或是蓝:

最后一个做不了,但是其他的都可以利用统计三元环/四元环个数在 \(O(m\sqrt{m})\) 的复杂度内解决,那么这题就做完了。

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=5e5;
int n,m;
ll res[12],cnt[N+10];
vector<pii> G[N+10],GG[N+10];
int vis[N+10];
bool cmp(int u,int v) {
	if(G[u].size()==G[v].size()) return u<v;
	return G[u].size()<G[v].size();
}
ll C(int n,int m) {
	if(n<m) return 0;
	if(m==2) return 1ll*n*(n-1)/2;
	if(m==3) return 1ll*n*(n-1)/2*(n-2)/3;
	if(m==4) return 1ull*n*(n-1)/2*(n-2)/3*(n-3)/4;
	return 114514;
}
int main() {
	scanf("%d %d",&n,&m);
	for(int i=1,x,y;i<=m;i++) {
		scanf("%d %d",&x,&y);
		G[x].pb({y,i}),G[y].pb({x,i});
	}
	res[1]=C(n,4),res[2]=m*C(n-2,2);
	for(int u=1;u<=n;u++) res[3]=C(G[u].size(),2)+res[3];
	res[4]=C(m,2)-res[3];
	res[3]*=(n-3);
	for(int u=1;u<=n;u++) res[5]+=C(G[u].size(),3);
	for(int u=1;u<=n;u++)
		for(pii i:G[u]) if(u<i.fi)
			res[6]+=1ll*(G[u].size()-1)*(G[i.fi].size()-1);
	for(int u=1;u<=n;u++)
		for(pii i:G[u])
			if(cmp(u,i.fi)) GG[u].pb(i);
	for(int u=1;u<=n;u++) {
		for(pii i:GG[u]) vis[i.fi]=i.se;
		for(pii i1:GG[u])
			for(pii i2:GG[i1.fi]) if(vis[i2.fi]) {
				res[7]++,res[8]+=G[u].size()+G[i1.fi].size()+G[i2.fi].size()-6;
				cnt[i1.se]++,cnt[i2.se]++,cnt[vis[i2.fi]]++;
			}
		for(pii i:GG[u]) vis[i.fi]=0;
	}
	for(int i=1;i<=m;i++) res[10]+=C(cnt[i],2);
	res[6]-=3*res[7],res[7]*=n-3;
	for(int u=1;u<=n;u++) {
		for(pii i1:G[u])
			for(pii i2:GG[i1.fi]) {
				res[9]+=vis[i2.fi];
				if(cmp(u,i2.fi)) vis[i2.fi]++;
			}
		for(pii i1:G[u])
			for(pii i2:GG[i1.fi])
				if(cmp(u,i2.fi)) vis[i2.fi]=0;
	}
	ll ans=res[1]-res[2]+res[3]+res[4]-res[5]-res[6]-res[7]+res[8]+res[9]-res[10];
	printf("%lld\n",abs(ans));
}

Puzzle in Inazuma

Source: XXII Open Cup, GP of Nanjing B
给定一个 \(n\) 个点的带权完全图 \(G\),现在你可以对这个图做至多 \(10^5\) 次如下操作,使其变成另一张带权完全图 \(H\)

  • 选取四个点 \(a,b,c,d\) 与权值 \(x\),使得边 \((a,b),(a,c),(a,d)\) 加上 \(x\),边 \((b,c),(b,d),(c,d)\) 减去 \(x\)

构造一组方案或判断无解。
\(4\le n\le 100\),边权在 \(-100\sim 100\) 之间。

Solution

发现操作的权值对总权值是不变的,这是一个判无解的情况。
先让 \(G\) 的边权对 \(H\) 做差,将操作目标转变为使得 \(G\) 边权为 \(0\)
接下来我们先来讨论 \(n\ge 6\) 的部分,首先,我们先称操作 \((a,b,c,d,x)\) 对应原题所述的操作:

考虑如下操作组:

  • \((d,a,b,e,-x)\)
  • \((d,a,b,c,x)\)
  • \((e,a,c,d,x)\)
  • \((e,a,b,c,-x)\)

这样的操作组对应着将 \((a,b),(a,e)\) 添加 \(x\),将 \((a,c),(a,d)\) 减少 \(x\),运用类似的方式,我们就可以添加一个新点 \(f\),操作 \((a,b),(a,e)\)\(x\)\((a,c),(a,f)\)\(x\),也就是说,我们可以运用两组操作做到 \((a,d)\) 减少 \(x\)\((a,f)\) 增大 \(x\)
这是一个一般的形式,我们可以通过这个方式直接构造出答案,所以 \(n\ge 6\) 时只要总权值相等就可以构造。

\(n=5\) 的时候,发现找不到 \(f\),考虑修改一波做法,我们发现可以 \(\text{swap}(c,e)\) 然后实现 \((a,b)\) 添加 \(2\times x\)\((a,e)\) 减少 \(2\times x\) 的操作。
那么能不能构造就取决于边权的奇偶性了,注意一次操作是可以改变 \(6\) 条边的奇偶性的,暴力枚举这 \(6\) 条边不和哪个点有关就可以做到 \(O(2^n)\) 的优秀复杂度。

\(n=4\) 的时候,操作恒定,可以直接列方程解。

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=100;
int n,G[N+10][N+10],G1[N+10][N+10],T[N+10][N+10];
struct node {int a,b,c,d,e;};
vector<node> ans;
void NoAnswer() {
	puts("-1");exit(0);
}
namespace n4 {
	void work(int a,int b,int c,int d,int x) {ans.pb({a,b,c,d,x});}
	void solve() {
		int sum=G[1][2]+G[1][3]+G[1][4];
		if(abs(sum-G[1][2])%2) NoAnswer();
		if(abs(sum-G[1][3])%2) NoAnswer();
		if(abs(sum-G[1][4])%2) NoAnswer();
		if(G[1][3]+G[2][4]!=0) NoAnswer();
		if(G[1][4]+G[2][3]!=0) NoAnswer();
		if(G[1][2]+G[3][4]!=0) NoAnswer();
		work(2,1,3,4,(sum-G[1][2])/2);
		work(3,1,2,4,(sum-G[1][3])/2);
		work(4,1,2,3,(sum-G[1][4])/2);
	}
}
namespace n5 {
	void add(int i,int j,int x) {
		G[i][j]+=x,G[j][i]+=x;
	}
	void add(int a,int b,int c,int d,int x) {
		add(a,b,x),add(a,c,x),add(a,d,x);
		add(b,c,-x),add(b,d,-x),add(c,d,-x);
		ans.pb({a,b,c,d,x});
	}
	void work(int a,int b,int c,int d,int e,int x) {//Make a->b,a->e plus x a->c a->d decrease x
		add(d,a,b,e,-x),add(d,a,b,c,x);
		add(e,a,c,d,x),add(e,a,b,c,-x);
	}
	void solve(int A,int D,int B,int x) {
		int C=1,E=1;
		while(C==A||C==D||C==B) ++C;
		while(E==A||E==B||E==C||E==D) ++E;
		work(A,B,C,D,E,x),work(A,B,D,E,C,x);
	}
	void solve() {
		for(int S=0;S<(1<<n);S++) {
			for(int i=1;i<n;i++)
				for(int j=i+1;j<=n;j++)
					T[i][j]=0;
			for(int i=1;i<=n;i++) if(S&(1<<(i-1))) {
				for(int j=1;j<n;j++)
					for(int k=j+1;k<=n;k++)
						if(i!=j&&i!=k) T[j][k]^=1;
			}
			bool FL=1;
			for(int i=1;i<n;i++)
				for(int j=i+1;j<=n;j++)
					if(T[i][j]!=abs(G[i][j])%2) FL=0;
			if(!FL) continue;
			for(int i=1;i<=n;i++)
				if(S&(1<<(i-1))) {
					int a,b,c,d;
					if(i==1) a=2,b=3,c=4,d=5;
					if(i==2) a=1,b=3,c=4,d=5;
					if(i==3) a=1,b=2,c=4,d=5;
					if(i==4) a=1,b=2,c=3,d=5;
					if(i==5) a=1,b=2,c=3,d=4;
					add(a,b,c,d,1);
				}
			for(int i=1;i<n;i++)
				for(int j=i+1;j<=n;j++) {
					if(G[i][j]==0) continue;
					solve(j,i,j==n?i+1:n,G[i][j]/2);
				}
		}
	}
}
namespace nn {
	void add(int i,int j,int x) {
		G[i][j]+=x,G[j][i]+=x;
	}
	void add(int a,int b,int c,int d,int x) {
		add(a,b,x),add(a,c,x),add(a,d,x);
		add(b,c,-x),add(b,d,-x),add(c,d,-x);
		ans.pb({a,b,c,d,x});
	}
	void work(int a,int b,int c,int d,int e,int x) {//Make a->b,a->e plus x a->c a->d decrease x
		add(d,a,b,e,-x),add(d,a,b,c,x);
		add(e,a,c,d,x),add(e,a,b,c,-x);
	}
	void solve(int x) {
		for(int i=2;i<=n;i++) if(G[x][i]) {
			int tt[3],tot=0;
			for(int j=1;j<=n;j++)
				if(j!=1&&j!=i&&j!=x) {
					tt[tot++]=j;
					if(tot==3) break;
				}
			int tmp=G[x][i];
			work(x,tt[0],tt[1],i,tt[2],tmp);
			work(x,tt[0],tt[1],1,tt[2],-tmp);
		}
	}
	struct node {int d,id;} a[N+10];
	bool cmp(node x,node y) {return x.d<y.d;}
	void solve() {
		for(int i=2;i<=n;i++) solve(i);
		// cerr<<"FUCK"<<endl;
		for(int i=2;i<=n;i++) a[i]={G[1][i],i};
		sort(a+2,a+n+1,cmp);
		// cerr<<"FUCK"<<endl;
		int L=2,R=n;
		while(L<R) {
			int tt[3],tot=0;
			for(int j=1;j<=n;j++) {
				if(j!=1&&j!=a[L].id&&j!=a[R].id) tt[tot++]=j;
				if(tot==3) break;
			}
			// cerr<<L<<" "<<R<<" "<<G[1][a[L].id]<<" "<<G[1][a[R].id]<<endl;
			//A 1 B tt[0] C tt[1] D a[R].id E tt[2] F a[L].id
			int tmp=min(abs(G[1][a[L].id]),abs(G[1][a[R].id]));
			work(1,tt[0],tt[1],a[R].id,tt[2],tmp);
			work(1,tt[0],tt[1],a[L].id,tt[2],-tmp);
			if(G[1][a[L].id]==0) L++;
			if(G[1][a[R].id]==0) R--;
		}
		// cerr<<"FUCK"<<endl;
	}
}
void print() {
	printf("%d\n",ans.size());
	for(node i:ans) printf("%d %d %d %d %d\n",i.a,i.b,i.c,i.d,i.e);
}
void check() {
	for(node i:ans) {
		G1[i.a][i.b]+=i.e,G1[i.b][i.a]+=i.e,
		G1[i.a][i.c]+=i.e,G1[i.c][i.a]+=i.e,
		G1[i.a][i.d]+=i.e,G1[i.d][i.a]+=i.e,
		G1[i.b][i.c]-=i.e,G1[i.c][i.b]-=i.e;
		G1[i.b][i.d]-=i.e,G1[i.d][i.b]-=i.e;
		G1[i.c][i.d]-=i.e,G1[i.d][i.c]-=i.e;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(G1[i][j]!=0) NoAnswer();
}
int main() {
	int sum=0;
	scanf("%d",&n);
	for(int i=1;i<n;i++)
		for(int j=i+1;j<=n;j++)
			scanf("%d",&G[i][j]);
	for(int i=1;i<n;i++)
		for(int j=i+1,x;j<=n;j++)
			scanf("%d",&x),G[i][j]-=x,G[j][i]=G[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			sum+=G[i][j],G1[i][j]=G[i][j];
	if(sum!=0) NoAnswer();
	if(n==4) n4::solve();
	else if(n==5) n5::solve();
	else nn::solve();
	check(),print();
}

Many LCS

Source: XXII Open Cup, GP of Southeastern Europe M
给定 \(K\),请构造两个 \(01\)\(S,T\),使得他们本质不同 LCS 的个数恰好为 \(K\),且他们的长度 \(\le 8848\)
\(1\le K\le 10^9\)

Solution

考虑这样一种构造:

\[S=0^{a_1}010^{a_2}01\ldots0^{a_m}01\\T=(01)^{n+m} \]

其 LCS 长度必定为 \(n+2m\),其中 \(m\)\(1\)\(n+m\)\(0\)
\(S\) 中取出的每一个形如 \(0^{b_1}010^{b_2}01\ldots 0^{b_m}01\) 都对应着原串的一类 LCS,其中 \(\sum b_i=n\)\(\forall i,0\le b_i\le a_i\)
对于 \(b\) 序列计数可以容斥,但是太麻烦,不妨假设 \(a_i>\frac{n}{2}\),这样子的话只会存在一个 \(b_i\) 爆限制,那么可能的 LCS 数就会变成:

\[\binom{n+m-1}{m-1}-\sum^{m}_{i=1}\binom{n+m-a_i-2}{m-1} \]

接下来考虑如何确定 \(m\),我们如果取 \(m=3\)——事实上对于每一个 \(x\),都可以用三个三角形数(\(\binom{n}{2}\))来表示——对于足够大的 \(K\)\(n\) 大概会变成 \(\sqrt{2K}\),所以我们考虑取 \(m=4\)
有一个未经证实的猜想是每一个 \(x\) 都可以用至多五个四边形数(\(\binom{n}{3}\))来表示,而且这个猜想有人好像已经跑出了 \(10^{10}\) 内一定没问题,而且在 \(1\sim 10^9\) 内只有 \(241\) 个数需要五个数,如果遇到了这种不幸的情况,我们只需要给 \(n\) 加上 \(1\)
那么,取 \(m=4\),找一个 \(n\) 出来,然后借助 \(01\) 背包,随便背出一个 \(a_i\),这题就解决了。

注意到有 \(a_i>\frac{n}{2}\) 的情况,如果 \(K\)\(n\) 也会变小,所以需要特判。事实上,我们只需要对 \(m=2\) 构造,构造:

\[S=0^{K+1}010^{K+1}01\\T=(01)^{K+1} \]

即可,至此,问题解决。

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
#define int long long
int K,v[5];
bitset<3000005> f[5];
int C3(int n) {return 1ll*n*(n-1)*(n-2)/6;}
signed main() {
	scanf("%lld",&K);
	f[0]=1;
	for(int i=1;i<=4;i++)
		for(int j=1;j<=210;j++)
			f[i]|=f[i-1]<<C3(j);
	if(K<=4400) {
		for(int i=1;i<=K+3;i++) putchar('0');
		putchar('1');
		for(int i=1;i<=K+3;i++) putchar('0');
		putchar('1'),putchar('\n');
		for(int i=1;i<=K+1;i++) printf("01");
		putchar('\n');
	}
	else {
		int n=1;
		while(C3(n+3)<K) n++;
		while(!f[4][C3(n+3)-K]) n++;
		ll ret=C3(n+3)-K;
		for(int i=4;i>=1;i--)
			for(int j=1;j<=210;j++)
				if(f[i-1][ret-C3(j)]) {
					ret-=C3(j),v[i]=j;
					break;
				}
		// cerr<<ret<<endl;
		for(int i=1;i<=4;i++) {
			for(int j=1;j<=n-v[i]+3;j++) putchar('0');
			putchar('1');
		}
		putchar('\n');
		for(int i=1;i<=n+4;i++) printf("01");
		putchar('\n');
	}
}

New Queries On Segment Deluxe

Source: XXII Open Cup, GP of Southeastern Europe B
给定一个大小为 \(k\times n\) 的矩阵 \(A\),根据其构造一个数列 \(B_j=\sum^k_{i=1}A_{i,j}\)
维护下列操作:

  • 1 t p l r x:新建一个版本,这个版本是由版本 \(t\)\(A_{p,i},i\in[l,r]\) 加上 \(x\) 得来的。
  • 2 t p l r y:新建一个版本,这个版本是由版本 \(t\)\(A_{p,i},i\in[l,r]\) 被覆盖为 \(y\) 得来的。
  • 3 t l r:新建一个版本,这个版本是 copy 的版本 \(t\),求出 \(\min^r_{i=l}B_i\)

\(1\le k\le 4\)\(1\le n\le 2.5\times 10^5\)\(1\le q\le 2\times 10^4\)\(|A_{i,j}|,|y|\le 10^8\)\(|x|\le 10^4\)

Soltuion

观察:\(k\) 小哉,还有版本,不难考虑使用可持久化线段树。
可是需要维护什么呢?我们考虑维护大力 \(2^k\) 维护每一种取行的最小值,然后你突然发现这题好像做完了,剩下的就是常规线段树。
这题是不是很水?

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=250000,Q=20000;
const ll inf=1e18;
int K,n,q,cnt,rt[Q+10];
ll a[N+10][5];
struct node {
	int ls,rs;
	ll add[5],cov[5];
	ll f[1<<4];
	void init() {
		for(int i=0;i<K;i++) add[i]=0,cov[i]=-inf;
		for(int i=0;i<(1<<K);i++) f[i]=-inf;
		ls=rs=0;
	}
	void print() {
		cerr<<ls<<" "<<rs<<endl;
		for(int i=0;i<K;i++) cerr<<add[i]<<" ";
		cerr<<endl;
		for(int i=0;i<K;i++) cerr<<cov[i]<<" ";
		cerr<<endl;
		for(int i=0;i<(1<<K);i++) cerr<<f[i]<<" ";
		cerr<<endl;
	}
} tr[N*4+Q*80+5];
void pushup(node a,node b, node &c) {
	for(int i=0;i<(1<<K);i++)
		c.f[i]=min(a.f[i],b.f[i]);
}
int newnode(int u) {
	int v=++cnt;
	tr[v].init();
	tr[v].ls=tr[u].ls,tr[v].rs=tr[u].rs;
	for(int i=0;i<K;i++)
		tr[v].add[i]=tr[u].add[i],
		tr[v].cov[i]=tr[u].cov[i];
	for(int i=0;i<(1<<K);i++)
		tr[v].f[i]=tr[u].f[i];
	return v;
}
int build(int p,int l,int r) {
	p=newnode(0);
	if(l==r) {
		for(int i=0;i<(1<<K);i++) {
			tr[p].f[i]=0;
			for(int j=0;j<K;j++) if(i&(1<<j))
				tr[p].f[i]+=a[l][j];
		}
		return p;
	}
	int mid=(l+r)>>1;
	tr[p].ls=build(tr[p].ls,l,mid);
	tr[p].rs=build(tr[p].rs,mid+1,r);
	pushup(tr[tr[p].ls],tr[tr[p].rs],tr[p]);
	return p;
}
void add(node &p,int x,ll v) {
	for(int i=0;i<(1<<K);i++)
		if(i&(1<<x)) p.f[i]+=v;
	p.add[x]+=v;
}
void cov(node &p,int x,ll v) {
	for(int i=0;i<(1<<K);i++)
		if(i&(1<<x)) p.f[i]=p.f[i^(1<<x)]+v;
	p.cov[x]=v,p.add[x]=0;
}
void pushdown(node &p) {
	p.ls=newnode(p.ls),p.rs=newnode(p.rs);
	// printf("pushdown to %d %d\n",p.ls,p.rs);
	for(int i=0;i<K;i++) {
		if(p.cov[i]!=-inf)
			cov(tr[p.ls],i,p.cov[i]),
			cov(tr[p.rs],i,p.cov[i]),p.cov[i]=-inf;
		if(p.add[i])
			add(tr[p.ls],i,p.add[i]),
			add(tr[p.rs],i,p.add[i]),p.add[i]=0;
	}
}
void changeadd(int p,int l,int r,int x,int y,int h,ll v) {
	if(x<=l&&r<=y) {
		add(tr[p],h,v);
		return;
	}
	int mid=(l+r)>>1;
	pushdown(tr[p]);
	if(x<=mid) changeadd(tr[p].ls,l,mid,x,y,h,v);
	if(y>mid) changeadd(tr[p].rs,mid+1,r,x,y,h,v);
	pushup(tr[tr[p].ls],tr[tr[p].rs],tr[p]);
}
void changecov(int p,int l,int r,int x,int y,int h,ll v) {
	if(x<=l&&r<=y) {
		cov(tr[p],h,v);
		return;
	}
	int mid=(l+r)>>1;
	pushdown(tr[p]);
	if(x<=mid) changecov(tr[p].ls,l,mid,x,y,h,v);
	if(y>mid) changecov(tr[p].rs,mid+1,r,x,y,h,v);
	pushup(tr[tr[p].ls],tr[tr[p].rs],tr[p]);
}
ll query(int p,int l,int r,int x,int y) {
	if(x<=l&&r<=y) return tr[p].f[(1<<K)-1];
	int mid=(l+r)>>1;pushdown(tr[p]);
	if(mid<x) return query(tr[p].rs,mid+1,r,x,y);
	if(mid+1>y) return query(tr[p].ls,l,mid,x,y);
	return min(query(tr[p].rs,mid+1,r,x,y),query(tr[p].ls,l,mid,x,y));
}
int main() {
	scanf("%d %d %d",&K,&n,&q);
	tr[0].init();
	for(int i=0;i<K;i++)
		for(int j=1;j<=n;j++)
			scanf("%lld",&a[j][i]);
	rt[0]=build(rt[0],1,n);
	for(int i=1;i<=q;i++) {
		int op,t,p,l,r;ll v;
		scanf("%d",&op);
		if(op==1) {
			scanf("%d %d %d %d %lld",&t,&p,&l,&r,&v);
			p--,rt[i]=newnode(rt[t]);
			changeadd(rt[i],1,n,l,r,p,v);
		}
		if(op==2) {
			scanf("%d %d %d %d %lld",&t,&p,&l,&r,&v);
			p--,rt[i]=newnode(rt[t]);
			changecov(rt[i],1,n,l,r,p,v);
		}
		if(op==3) {
			scanf("%d %d %d",&t,&l,&r);
			rt[i]=rt[t];
			printf("%lld\n",query(rt[t],1,n,l,r));
		}
	}
}

LIS Counting

Source: XXII Open Cup, GP of Southeastern Europe D
给定 \(n,m,P\),对长度为 \(n\times m\) 的排列且其 LIS 长度为 \(n\),LDS 长度为 \(m\) 计数。
定义 \(f(pos,val)\) 表示强制第 \(pos\) 位为 \(val\) 的排列个数,输出所有的 \(f(pos,val)\)\(P\) 取模的结果。
\(1\le n\times m\le 100\)\(10^8\le P\le 10^9\),且 \(P\) 一定为素数。

Solution

考虑没有强制怎么做,发现还是没有什么很好的做法。
考虑问题模型的转化,设 \(inc_i\) 表示以 \(i\) 结尾的 LIS 长度,\(dec_i\) 表示以 \(i\) 结尾的 LDS 长度,对于一个满足条件的排列 \(P\),构造 \(n\times m\) 的矩阵 \(A,B\)\(A\)\((inc_i,dec_i)\) 处填 \(i\)\(B\)\((inc_i,dec_i)\) 处填 \(P_i\)

观察 1:\(A\) 的每行每列均单调上升。

证明:对于每一对 \(i<j\),有 \(inc_i<inc_j\) 或者 \(dec_i<dec_j\),那么必然不存在 \(A_{i,j}>A_{i,j+1}\) 或者 \(A_{i,j}>A_{i+1,j}\)

观察 2:\(B\) 的每行单调下降,每列单调上升。

证明,考虑反证,首先一个事实是不存在多个相同的 \(B_{i,j}\),现在假设 \(B_{i,j}<B_{i,j+1}\),那么由 \(A_{i,j}<A_{i,j+1}\),可得 \(P_{A_{i,j}}<P_{A_{i,j+1}}\),所以 \(inc_{A_{i,j}}<inc_{A_{i,j+1}}\),与事实不符,所以 \(B_{i,j}>B_{i,j+1}\)
单调上升的证明类似,这里不再赘述。

观察 3:\((A,B),P\) 一一对应。

证明:首先 \(P\to (A,B)\) 显然,考虑 \((A,B)\to P\),现在假设我们已经知道了 \((A,B)\),并推得了一个 \(P\),显然 \(P\) 的 LIS 长度至少为 \(n\),LDS 长度至少为 \(m\),因为每一行对应一个长度为 \(M\) 的子序列,每一列对应一个长度为 \(N\) 的子序列。
如果说存在一个大小为 \(n+1\) 的 LIS,考虑到 \(B\) 只有 \(n\) 行,那么必然有两个点在同一行,即,两个值 \(inc\) 相等,但是每一行需要递减,所以不可能。

这告诉我们,要想计数 \(P\),计数 \((A,B)\) 就可以了,而且 \(f(pos,val)\) 就恰恰对应了 \(f(A_{i,j},B_{i,j})\)

考虑 DP,记录矩阵每一列已经填了多少个数,求方案数,从低到高枚举现在填那些数,转移是平凡的。
再另外求出某个数填在 \((i,j)\) 的方案数,那么 \(f(pos,val)=\sum_{i,j} g_A(pos,i,j)\times g_B(val,i,j)\)
时间复杂度:\(O(Can\ Solve\ It)\)

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=100;
int n,m,mod,a[N+10],b[N+10],g[N+10][N+10][N+10];
bool FL;
unordered_map<ull,int> f[N+10];
ull encode(int *a) {
	ull ret=0;
	for(int i=1;i<=n;i++) ret=ret*(m+1)+a[i];
	return ret;
}
void decode(ull x) {
	for(int i=n;i;i--) a[i]=x%(m+1),x/=(m+1);
}
int main() {
	scanf("%d %d %d",&n,&m,&mod);
	if(n>m) FL=1,swap(n,m);
	f[0][0]=1;
	// cerr<<"FUCK0"<<endl;
	for(int i=0;i<n*m;i++)
		for(auto S:f[i]) {
			decode(S.fi);
			for(int j=1;j<=n;j++) if(a[j]!=m&&(j==1||a[j-1]>a[j])) {
				a[j]++;
				(f[i+1][encode(a)]+=S.se)%=mod;
				a[j]--;
			}
		}
	// cerr<<"FUCK1"<<endl;
	for(int i=1;i<=n*m;i++)
		for(auto S:f[i-1]) {
			decode(S.fi);
			for(int j=1;j<=n;j++) if(a[j]!=m&&(j==1||a[j-1]>a[j])) {
				a[j]++;
				for(int k=1;k<=n;k++) b[n-k+1]=m-a[k];
				g[j][a[j]][i]=(g[j][a[j]][i]+1ll*S.se*f[n*m-i][encode(b)]%mod)%mod;
				a[j]--;
			}
		}
	// cerr<<"FUCK2"<<endl;
	for(int pos=1;pos<=n*m;pos++) {
		for(int j=1;j<=n*m;j++) {
			int val=(FL?n*m-j+1:j),ans=0;
			for(int a=1;a<=n;a++)
				for(int b=1;b<=m;b++)
					ans=(ans+1ll*g[a][b][pos]*g[a][m-b+1][val]%mod)%mod;
			printf("%d ",ans);
		}
		puts("");
	}
	// cerr<<"FUCK3"<<endl;
}

Colourful Permutation Sorting

Source: XXII Open Cup, GP of Southeastern Europe I
给定一个排列 \(p\),及每一个位置的颜色,你可以进行如下的两个操作来使得排列被排序:

  • 交换 \(p_i,p_j\),花费 \(S\)
  • 取出第 \(i\) 种颜色的全部元素并自由插回,花费 \(C_i\)

\(1\le n\le 10^5\),颜色数 \(1\le k\le 5\)\(0\le S,C_i\le 10^9\)

Solution

观察:一种颜色被操作至多 \(1\) 遍,两种操作相互独立。

意味着我们可以考虑先做操作 \(1\) 再做操作 \(2\)

如果不使用操作二,那么贡献就是 \(S\times (n-\text{cyc})\)

发现 \(k\) 小哉,直接暴力枚举哪些颜色被做了操作 \(2\),对于操作 \(2\) 我们其实等价于合并了若干点,使得原本的置换图变化形态。
为选定的颜色开虚点,根据原本的排列所对应的置换图,对于每一个置换环,将点所对应颜色的虚点按顺序连边,注意要额外处理没有选定颜色的置换环。
此时的贡献就是选定颜色的贡献加上 \(S\times (n-\text{cyc})\)\(\text{cyc}\) 是选出的环个数。
贡献最小化意味着想要在这个 \(k\) 个点的有向图求出尽可能多的环,接下来考虑这个问题。

观察:从小到大取走全部环最优。

首先,如果一个环经过一个自环,那么把自环分开最优。
其次,如果两个环交于一个二元环 \((i,j)\),设其是 \(x\to i\to j\to x\)\(y\to j\to i\to y\),可以拆分成 \(x\to i \to y \to j\to x\)\(i\to j\to i\),所以此时不会影响答案。
接下来考虑三元环 \((i,j,k)\),若存在三个环分别使用了 \((i,j),(j,k),(i,k)\) 这些边,那么他们的大小必然 \(\ge 3\),也就是说我这里会牵扯到 \(6\) 个点,可是只存在 \(5\) 个点,必然会有两个环交于同一点。
考虑这样一种构造:我们选了 \(i\to j\to y\to i\)\(i\to x\to k\to i\)\(j\to k\to z\to j\),构造 \(i\to j\to k\)\(i\to x\to k\to z\to j\to y\to i\),最后一个环过大,必然存在两个相同的点,可以在这里拆开,那么环数不变。
接下来考虑四元环 \((i,j,k,l)\),其实类似于三元环的证明,同样可以得到贪心取最优。

暴力枚举加建模,然后暴力 DFS 找环,时间复杂度大概 \(O(2^k (k+n))\)

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=1e5;
int n,K,S,cost[6],p[N+10],col[N+10],G[6][6],vis[N+10];
bool dfs(int u,int t,int k) {
	if(!k) return u==t;
	for(int i=0;i<K;i++) if(G[u][i]) {
		G[u][i]--;
		if(dfs(i,t,k-1)) return 1;
		G[u][i]++;
	}
	return 0;
}
int main() {
	int T;scanf("%d",&T);
	while(T--) {
		scanf("%d %d",&n,&K);
		scanf("%d",&S);
		for(int i=0;i<K;i++) scanf("%d",&cost[i]);
		for(int i=1;i<=n;i++) scanf("%d",&p[i]);
		for(int i=1;i<=n;i++) scanf("%d",&col[i]),col[i]--;
		ll ret=1e18;
		for(int st=0;st<(1<<K);st++) {
			ll sum=0;int cyc=0;
			for(int i=0;i<K;i++) if(st&(1<<i)) sum+=cost[i];
			for(int i=0;i<K;i++)
				for(int j=0;j<K;j++)
					G[i][j]=0;
			for(int i=1;i<=n;i++) vis[i]=0;
			for(int i=1;i<=n;i++) if(!vis[i]&&(st&(1<<col[i]))) {
				int j=p[i];vis[i]=1;
				for(;!(st&(1<<col[j]));j=p[j]) vis[j]=1;
				G[col[i]][col[j]]++;
			}
			for(int i=1;i<=n;i++) if(!vis[i]) {
				cyc++;
				for(int j=i;!vis[j];j=p[j]) vis[j]=1;
			}
			for(int i=1;i<=K;i++)
				for(int j=0;j<K;j++)
					while(dfs(j,j,i)) cyc++;
			sum+=1ll*S*(n-cyc);
			ret=min(ret,sum);
			// cerr<<sum<<" "<<st<<" "<<cyc<<endl;
 		}
 		printf("%lld\n",ret);
	}	
}

Bruteforce

Source: XXII Open Cup, GP of IMO B
给定 \(k,w\) 和一个长度为 \(n\) 的序列 \(a\),定义这个序列的权值为:

  • \(a\) 序列排序后的数组称作 \(b\)
  • 计算 \(\sum_{i=1}^n \lfloor \frac{b_i\times i^k}{w}\rfloor\)

动态修改,每次修改后求权值。
\(1\le n\le 10^5\)\(1\le k,w\le 5\)\(0\le a_i\le 10^5\)

Solution

一个显然的事实是 \(\lfloor \frac{x}{w}\rfloor=\frac{x-x\bmod w}{w}\),考虑基于这个维护 \(\sum^n_{i=1} b_i\times i^k\)\(\sum^n_{i=1} (b_i\times i^k)\bmod w\)
\(b\) 要求是排序之后的,想到使用权值线段树来维护这坨东西。
接下来先考虑后面这个式子,考虑维护 \(cnt_{i,j}\) 来表示 \(b_c\mod w =i\)\(c\mod w=j\) 的数字个数,插入时需要考虑当前子树的大小,合并时需要注意左子树的大小对右子树的影响。
考虑前面,对于每一个节点维护 \(f_i\) 表示现在的指数为 \(i\) 的式子值,插入与上述同理。考虑合并:首先左子树是直接加上,而右子树,则需要考虑左子树大小对右子树影响,也就是说要求出 \(\sum b_i\times (i+x)^k\),对于 \((i+x)^k\) 使用二项式定理拆开,变成 \(\sum b_i \times i^j\times x^{k-j}\times \binom{k}{j}\)\(\sum b_i\times i^j\) 已被处理,由此,可以在 \(O(k^2)\) 下完成合并。
总时间复杂度 \(O((w^2+k^2)(n+q)\log V)\)

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=1e5,mod=998244353;
int n,K,w,a[N+10],cnt[N*4+10][5][5],f[N*4+10][6],siz[N*4+10],g[6];
int C(int n,int m) {
    if(m>n) return 0;
    ll ret=1;
    for(int i=n;i>=n-m+1;i--) ret=1ll*ret*i;
    for(int i=1;i<=m;i++) ret/=i;
    return ret%mod;
}
int fpow(int x,int y) {
    int ret=1;
    for(;y;y>>=1) {
        if(y&1) ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;
    }
    return ret;
}
void pushup(int p) {
    for(int i=0;i<w;i++) for(int j=0;j<w;j++)
         cnt[p][i][j]=(cnt[p*2][i][j]+cnt[p*2+1][i][(j-siz[p*2]%w+w)%w])%mod;
    for(int i=0;i<=K;i++) {
        f[p][i]=f[p*2][i];
        for(int j=0;j<=i;j++)
            f[p][i]=(f[p][i]+(1ll*f[p*2+1][j]*C(i,j)%mod*fpow(siz[p*2],i-j)%mod))%mod;
    }
    siz[p]=siz[p*2]+siz[p*2+1];
}
void insert(int p,int l,int r,int v,int num) {
    if(l==r) {
        if(num>0) cnt[p][v%w][(siz[p]+1)%w]=(cnt[p][v%w][(siz[p]+1)%w]+num)%mod;
        else cnt[p][v%w][siz[p]%w]=(cnt[p][v%w][siz[p]%w]+num)%mod;
        if(num>0) for(int i=0;i<=K;i++) f[p][i]=(f[p][i]+1ll*v*fpow(siz[p]+num,i)%mod*num%mod+mod)%mod;
        else for(int i=0;i<=K;i++) f[p][i]=(f[p][i]+1ll*v*fpow(siz[p],i)%mod*num%mod+mod)%mod;
        siz[p]+=num;
        return;
    }
    int mid=(l+r)>>1;
    if(v<=mid) insert(p*2,l,mid,v,num);
    else insert(p*2+1,mid+1,r,v,num);
    pushup(p);
}
int main() {
    scanf("%d %d %d",&n,&K,&w);
    for(int i=1;i<=n;i++) {
        scanf("%d",&a[i]);
        insert(1,0,N,a[i],1);
        // printf("%d\n",f[1][K]);
    }
    int q;scanf("%d",&q);
    while(q--) {
        int x,y;scanf("%d %d",&x,&y);
        insert(1,0,N,a[x],-1),a[x]=y;
        insert(1,0,N,y,1);
        int ans=f[1][K];
        // printf("%d\n",ans);
        for(int i=1;i<w;i++)
            for(int j=1;j<w;j++)
                ans=(ans-1ll*cnt[1][i][j]*((i*fpow(j,K))%w)%mod+mod)%mod;
        printf("%lld\n",1ll*ans*fpow(w,mod-2)%mod);
        // cerr<<cnt[1][1][1]<<endl;
        // printf("%d\n",ans);
    }
}

K-onstruction

Source: XXII Open Cup, GP of IMO K
给定 \(K\),构造一个可重整数集 \(A\),满足如下条件:

  • \(1\le |A|\le 30\)
  • \(|A_i|\le 10^{16}\)
  • 恰有 \(k\)\(A\) 的子集满足子集和为 \(0\)

\(1\le K\le 10^6\),有 \(10^3\) 组。

Solution

假设我们已经求出了一个集合 \(S\),使得恰有 \(x\) 个子集和为 \(0\),同时 \(S\) 内没有 \(0\),而且 \(S\) 的总和也不为 \(0\)。假设 \(P\) 是集合 \(S\) 中正数的总和,\(N\) 是集合 \(S\) 中负数的总和,不失一般性的,假设 \(P>-N\),考虑扩展这个集合。
现在我们往这个集合内加入一些 \(P\) 的非 \(0\) 倍数,设这些加入的数的集合是 \(T\),考虑 \(S\cup T\) 的子集为 \(0\) 的个数,这个值等于 \(x\times\) \(T\) 中子集和为 \(0\) 的个数加上在 \(T\) 中取 \(-P\) 的方案数(此时 \(S\) 内取 \(P\))。
不难发现 \(S\cup T\) 仍然满足 \(S\) 的条件,可以继续扩展。

容易发现上面所说的对应一个 DP,枚举 \(T\),假设其长度为 \(n\),同时求出子集和为 \(-P\) 的集合个数,那么我们是可以转移的,但是 \(T\) 太多了,不好转移。
考虑利用一些基本的元素来构造 \(T\),我们仅从 \(\{5,4,3,2,1,-1,-2,-3,-4,-5\}\) 这个集合的每一个子集出发,并且同时也使用这一些子集转移,这样子转移的复杂度就被拉下来了。
此时的转移对应一个最短路,记录方案即可。

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
vector<int> h[210][210];
int A,B;
void get(vector<int> S) {
    A=B=0;
    for(int i=0;i<(1<<S.size());i++) {
        int sum=0;
        for(int j=0;j<S.size();j++)
            if(i&(1<<j)) sum+=S[j];
        // cerr<<i<<" "<<sum<<endl;
        if(sum==0) A++;
        if(sum==-1) B++;
    }
    // cerr<<A<<" "<<B<<endl;
}
void dfs(int dep,int las,int sum,vector<int> S) {
    // cerr<<dep<<" "<<las<<" "<<sum<<endl;
    if(sum>=0) {
        get(S);
        if(!h[A][B].size()) h[A][B]=S;
        else if(S.size()<h[A][B].size()) h[A][B]=S;
    }
    else if(las<=0) return;
    if(dep==10) return;
    for(int i=las;i>=-5;i--) if(i) {
        vector<int> T=S;T.pb(i);
        dfs(dep+1,i,sum+i,T);
    } 
}
const int M=1e6;
vector<int> st[M+10];
int dis[M+10],pre[M+10],vis[M+10];
priority_queue<pii> pq;
void dij() {
    for(int i=1;i<=M;i++) dis[i]=M;
    dis[1]=1,pq.push({-dis[1],1});
    while(pq.size()) {
        int u=pq.top().se;pq.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=2;i<=56;i++)
            for(int j=0;j<=64;j++) {
                if(u*i+j>M) break;
                if(h[i][j].size()&&(dis[u*i+j]>dis[u]+(int)h[i][j].size())) {
                    dis[u*i+j]=dis[u]+h[i][j].size();
                    pre[u*i+j]=u,st[u*i+j]=h[i][j];
                    if(!vis[u*i+j]) pq.push({-dis[u*i+j],u*i+j});
                }
            }
    }
}
vector<ll> ans;ll tmp;
void calc(int x) {
    if(x==1) {
        ans.pb(1);tmp++;
        return;
    }
    calc(pre[x]);ll o=0;
    for(int i:st[x]) {
        ans.pb(1ll*tmp*i);
        if(i>0) o+=i;
    }
    tmp+=o*tmp;
}
void solve() {
    int x;scanf("%d",&x);
    ans.clear(),tmp=0,calc(x);
    printf("%d\n",(int)ans.size());
    for(ll i:ans) printf("%lld ",i);
    puts("");
}
int main() {
    // get({2,-2});
    // system("pause");
    dfs(1,5,0,{});dij();
    int T;scanf("%d",&T);
    while(T--) solve();
}

Deleting

Source: XXII Open Cup, GP of IMO D
给定一个数组 \([1,2,\ldots,n]\),你可以每次选择相邻的两个元素 \(i,j\) 并以 \(a_{i,j}\) 的代价将他们删除,同时将左右合并。
总代价是每一步代价的最大值,求出最小的代价。
\(2\le n\le 4000\)\(n\) 是个偶数,\(1\le A_{i,j}\le (\frac n 2)^2\)

Solution

显然的有一个区间 DP,设 \(f_{l,r}\) 表示删除 \(l,r\) 所需要的代价最小是多少,转移显然,此处略去。
发现这竟然是 \(O(n^3)\) 的完全过不去,所以需要优化。

考虑二分一个答案 \(x\),接下来仅需要考虑 \(f_{l,r}\) 可否被删除。
枚举每一个状态 \(f\),从目前未被转移的某一段 \((l,r)\),考虑他能否被 \(f_{l+1,r-1}\) 转移过来,如果可以,考虑转移区间 DP 的分割区间部分,实际上将 \(r\) 看成中间点即可,这里就可以使用「世界上最好的数据结构(题解语)」——bitset 来优化。
综上,我们提出了一个 \(O(\frac{n^3}{w}\log n^2)\) 的做法,由于跑不满的原因可过。

这题有一个 \(O(\frac{n^3}{w})\) 的做法,但是:

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=4000;
int n,C[N+10][N+10];
bitset<N+10> f[N+10];
bool check(int x) {
	for(int i=1;i<=n;i++) f[i].reset(),f[i][i-1]=1;
	for(int l=n;l>=1;l--)
		for(int r=l+1;r<=n;r+=2) {
			if(f[l][r]) continue;
			if(C[l][r]<=x&&f[l+1][r-1]) f[l][r]=1,f[l]|=f[r+1];
		}
	return f[1][n];
}
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j+=2)
			scanf("%d",&C[i][j]);
	int L=1,R=n*n/4;
	while(L<=R) {
		int mid=(L+R)>>1;
		if(check(mid)) R=mid-1;
		else L=mid+1;
	}
	printf("%d\n",R+1);
}

Cilantro

Source: XXII Open Cup, GP of Korea B
给定一个 \(01\) 入栈序列和一个 \(01\) 出栈序列,求出可以第一个出栈的元素并输出他们的编号和。
\(1\le n\le 5\times 10^6\)

Solution

观察 1:对于任意一个 \(01\) 入栈序列,我们可以得到任意一个 \(01\) 出栈序列,只要他们 \(01\) 个数相同,但是我们并不知道编号。
观察 2:若 \(i<j\),且 \(j\) 可行,那么 \(i\) 也可行。

根据上面的观察 2,我们可以利用二分,二分出一个最大的 \(x\),使得 \(x\) 可行,时间复杂度 \(O(n\log n)\),大力卡常可以通过。

接下来,我们需要将这个做法优化到线性。
考虑从后往前扫描每一个点值,同时维护当前是否可以存在入栈出栈序列的对应关系。
考虑这样一个策略,先选一些点,把它们全部丢到后面。
如果此时存在这个对应关系,并且他们可以被选,我们就把当前 \(S\) 所对应的点丢到选的点集里面。
否则的话再来维护这个对应关系。

维护这个东西可以做到 \(O(n)\)

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=5e6;
int n,las,id=0;
char S[N+10],T[N+10];
int s[N+10],t[N+10];
ll ans=0;
int main() {
	scanf("%d",&n);
	scanf("%s%s",S+1,T+1);
	for(int i=n;i>=1;i--)
		if(las==0&&S[id+1]==T[i]) id++;
		else {
			las-=(S[i+id]=='Y'?1:-1);
			las+=(T[i]=='Y'?1:-1);
		}
	for(int i=1;i<=id;i++) if(S[i]==T[1]) ans+=i;
	printf("%lld\n",ans);
}

Flowerbed Redecoration

Source: XXII Open Cup, GP of Korea D
给定一个 \(n\times m\) 的字母矩形,现在你想要做一些操作:

  • 首先,把一个 \(d\times d\) 的框子框在以 \((1,1)\) 为左上角的位置。
  • 将框子对应的矩形顺时针整体旋转 \(90\) 度,然后将框子向右移动 \(x\) 格,如果不能移动,则将框子向下移动 \(y\) 格,并将框子横向移动到第一列。
  • 重复上述操作直到不能再操作。

输出最后的字母矩形。
\(1\le n\times m\le 10^6\),不用担心会把框子移出去。

Solution

考虑置换形式。
首先依据物理知识,我们把移动框子转化成循环移动矩形。
也就是说,我们考虑 \(d\times d\) 框子内的矩形整体旋转,设这里可以形成的置换为 \(A\);向左循环位移 \(x\) 位形成的置换为 \(B\);向上循环位移 \(y\) 位形成的置换为 \(C\)
那么如果将操作从向下移动 \(y\) 格处分割开,那么分割开的每一个部分为 \(H=(AB)^{k}A\),可以使用置换快速幂预处理。
那么整个操作也就是 \((HC)^{k}H\),注意这里的 \(k\) 与上述的 \(k\) 不是同一个定义。
利用置换快速幂可以做到 \(O(nm\log nm)\)

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=1e6;
int n,m,x,y,d;
vector<int> A,B,C,D,E,H,ans,c,rev;
char ch[N+10];
int bh(int x,int y) {return (x-1)*m+y;}
vector<int> merge(vector<int> a,vector<int> b) {
	c.resize(n*m+1);
	for(int i=1;i<=n*m;i++) c[i]=b[a[i]];
	return c;
}
vector<int> ret;
vector<int> fpow(vector<int> x,int y) {
	ret.resize(n*m+1);
	for(int i=1;i<=n*m;i++) ret[i]=i;
	for(;y;y>>=1) {
		if(y&1) ret=merge(ret,x);
		x=merge(x,x);
	}
	return ret;
}
int main() {
	scanf("%d %d %d %d %d",&n,&m,&y,&x,&d);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>ch[bh(i,j)];
	A.resize(n*m+1),B.resize(n*m+1),C.resize(n*m+1);
	D.resize(n*m+1),E.resize(n*m+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(i<=d&&j<=d) A[bh(i,j)]=bh(j,d-i+1);
			else A[bh(i,j)]=bh(i,j);
	int t1=(m-d)/x+1,t2=(n-d)/y+1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			if(i<=d) B[bh(i,j)]=bh(i,(j-x-1+m)%m+1);
			else B[bh(i,j)]=bh(i,j);
			C[bh(i,j)]=bh((i-y-1+n)%n+1,j);
			if(i<=d) D[bh(i,j)]=bh(i,(j+1ll*t1*x%m-1)%m+1);
			else D[bh(i,j)]=bh(i,j);
			E[bh(i,j)]=bh((i+1ll*t2*y%n-1)%n+1,j);
		}
	H=merge(fpow(merge(A,B),(m-d)/x+1),D);
	ans=merge(fpow(merge(H,C),(n-d)/y+1),E);
	rev.resize(n*m+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			rev[ans[bh(i,j)]]=bh(i,j);
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++)
			printf("%c",ch[rev[bh(i,j)]]);
		puts("");
	}
}

Thanks to MikeMirzayanov

Source: XXI Open Cup, GP of Nizhny Novgorod F
CF1427D,加强版,\(n\le 20000\)\(q\le 120\)

Solution

先改一波操作,每次只翻转区间内的元素,最后再来处理是否被需要再翻转一次。

考虑一个简单的问题——序列只有 \(0/1\) 的情况,不妨考虑先将相连的 \(0/1\) 缩起来,然后,假设我们现在是 \(0101010\cdots\),按奇偶性划分区间,\(0|10|1|01|0\cdots\),可以得到 \(00111000\cdots\) 的结果,会使段数减少大约 \(\frac 1 3\)
所以对于序列只有 \(0/1\) 的情况,可以直接得到 \(\log_3n\) 的操作步数。

结合 \(0/1\) 的性质,按位考虑。
从高到低枚举每一位,对于每一位,先按照上述做法排序,对于连在一段的 \(01\),可以继续使用上述的做法分治下去。
注意到每一层可以并行,所以我们的操作步数是 \(\log_3n\log_2n\) 级别的。

Code

几乎是贺的,太难了。

/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
#define pb push_back
#define fi first
#define se second
const int N=20000;
int n,a[N+10],b[N+10],c[N+10];
void rev(vector<int> vi) {
	reverse(vi.begin(),vi.end());
	int L=0,R=n;
	for(int x:vi) {
		R-=x;
		for(int j=0;j<x;j++) b[L+j]=a[R+j];
		L+=x;
	}
	for(int i=0;i<n;i++) a[i]=b[i];
}
vector<int> vii;
vector<vector<int> > ans;
void solve(int L,int R,int dep) {
    // cerr<<"solve "<<L<<" "<<R<<" "<<dep<<endl;
	int tmp=-1;bool FL=1;
	for(int i=L;i<R;i++) {
		int now=a[i]>>dep&1;
		if(now<tmp) FL=0;
		tmp=now;
	}
	if(FL) {
		vii.pb(R-L);
		return;
	}
	vector<int> res;
	res.clear();
	int pre=-1,cnt=L;
	for(int i=L;i<R;i++) {
		int now=a[i]>>dep&1;
		if(pre==-1) pre=now;
		if(pre!=now) pre=now,res.pb(i-cnt),cnt=i;
	}
	res.pb(R-cnt);
	for(int i=0;i<(int)res.size();i+=3) {
		vii.pb(res[i]);
		if(i+1==(int)res.size()) continue;
		if(i+2==(int)res.size()) vii.pb(res[i+1]);
		else vii.pb(res[i+1]+res[i+2]);
	}
}
bool check(int dep) {
	int pre=-1;
	for(int i=0;i<n;i++) {
		int ret=a[i]>>dep;
		if(ret<pre) return 0;
		pre=ret;
	}
	return 1;
}
int main() {
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&a[i]),a[i]--;
	for(int i=20;i>=0;i--) {
		if((1<<i)>=n) continue;
		while(!check(i)) {
			vii.clear();
			int pre=-1,cnt=0;
			for(int j=0;j<n;j++) c[j]=a[j]>>(i+1);
			// for(int j=0;j<n;j++) printf("%d ",c[j]);puts("");
			for(int j=0;j<n;j++) {
				if(pre==-1) pre=c[j];
				if(c[j]!=pre) {
					// cerr<<cnt<<" "<<j<<endl;
					solve(cnt,j,i),cnt=j,pre=c[j];
				}
			}
			solve(cnt,n,i);
			ans.pb(vii),rev(vii);
		}
	}
	printf("%d\n",(int)ans.size());
	for(auto v:ans) {
		printf("%d ",(int)v.size());
		for(int x:v) printf("%d ",x);
		puts("");
	}
}

Command and Conquer: Red Alert 2

Source: XXII Open Cup, GP of Xi'An H
给定一个三维空间,现在你在 \((-10^{100},-10^{100},-10^{100})\) 处,你每次可以使一维坐标 \(+1\),空间上有 \(n\) 个敌人,给出他们的坐标。
如果 \(\max(|x_s-x_e|,|y_s-y_e|,|z_s-z_e|)\le k\),则代表你可击杀这个敌人,敌人不会动,求出击杀所有敌人所需最小的 \(k\)
多测,\(1\le n\le 5\times 10^5\)\(\sum n\le 2\times 10^6\)

Solution

先将问题转化一下,空间结构上是以我所在的位置为中心打一个正方体,转化为以我所在的位置为左上角打一个正方体。
考虑现在能打到的敌人,如果我们的三维中的任意一维不是最小,我们贪心的移动到最小的位置,显然能打到的不会少。
考虑全部移动到了最小的维度,此刻我们必须开打,同样的,我们考虑贪心,打掉所需 \(k\) 最小的敌人。
上述东西可以使用 set 维护,\(O(n\log n)\)

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ROF(i,a,b) for(int i=a;i>=b;i--)
const int N=5e5;
int n,ans=0,x[N+10],y[N+10],z[N+10];
set<pii> A,B,C;
void solve() {
	scanf("%d",&n);
	A.clear(),B.clear(),C.clear();
	FOR(i,1,n) {
		scanf("%d %d %d",&x[i],&y[i],&z[i]);
		A.insert(mp(x[i],i)),B.insert(mp(y[i],i)),C.insert(mp(z[i],i));
	}
	ans=0;
	while(A.size()) {
		pii a=*A.begin(),b=*B.begin(),c=*C.begin();
		int cx=a.fi,cy=b.fi,cz=c.fi;
		vector<pii> tmp;
		tmp.pb({max({abs(x[a.se]-cx),abs(y[a.se]-cy),abs(z[a.se]-cz)}),a.se}),a=b;
		tmp.pb({max({abs(x[a.se]-cx),abs(y[a.se]-cy),abs(z[a.se]-cz)}),a.se}),a=c;
		tmp.pb({max({abs(x[a.se]-cx),abs(y[a.se]-cy),abs(z[a.se]-cz)}),a.se});
		sort(tmp.begin(),tmp.end());
		ans=max(ans,tmp[0].fi);
		A.erase(mp(x[tmp[0].se],tmp[0].se));
		B.erase(mp(y[tmp[0].se],tmp[0].se));
		C.erase(mp(z[tmp[0].se],tmp[0].se));
	}
	printf("%d\n",(ans+1)/2);
}
int main() {
	int T;scanf("%d",&T);
	FOR(i,1,T) solve();
}

Typing Contest

Source: XXII Open Cup, GP of Xi'An I
选拔 \(n\) 个人参加打字比赛,每个人两个属性 \(s_i,f_i\),一个人被加进团队内的贡献是 \(s_i\times (1-\sum_{j\in S,j\not=i}f_if_j)\)
求出最大贡献。
\(1\le n\le 100\)\(\sum n\le 2000\)\(1\le s_i\le 10^{12}\)\(0\le f_i\le 1\)

Solution

\(f_i\) 是浮点数,先乘上一个 \(100\) 来规避浮点数,那么单点贡献会变成 \(\frac 1 {10000} s_i(10000-\sum_{j\in S,j\not=i}f_if_j)\),下文忽略前方常数。
不难发现单点贡献可以变为 \(s_i(10000-f_i(\sum_{j\in S} f_j-f_i))\),如果固定 \(\sum_{j\in S} f_j\) 的话,可以快速得知贡献。
不妨枚举 \(\sum_{j\in S} f_j\),设其为 \(F\),那么单点贡献是 \(s_i(10000-f_i(F-f_i))\),问题变为典中典 \(01\) 背包。
发现复杂度是 \(O((\sum f_i)^2 n)\),不可过。
实际上,可以证明的是,\(F\) 的枚举最大值是 \(100\sqrt{n+2}\),这样复杂度稳过,证明如下:

\(t\) 是某一个位于 \(S\) 的集合,\(k\)\(S\) 的大小。
考虑到有:

\[s_t(10^4-f_t(\sum^k_{i=1}f_i-f_t))>0 \]

拆开得:

\[f_t\sum^k_{i=1}f_i-f_t^2<10^4 \]

对所有值求和可以得到:

\[(\sum^k_{i=1}f_i)^2-\sum^k_{i=1}f_t^2<10^4k(A) \]

换个角度来看,对原式左右 \(\times f_t\)

\[f_t^2\sum^k_{i=1}f_i-f_t^3<10^4f_t \]

对所有值求和可以得到:

\[\sum^k_{i=1} f_i^2-\frac{\sum_{i=1}^k f_i^3}{\sum^k_{i=1} f_i}<2\times 10^4 \]

移项可得:

\[\sum^k_{i=1} f_i^2<10^4+\frac{\sum^k_{i=1}f_i^3}{\sum^k_{i=1} f_i}\le 2\times 10^4(B) \]

联立 \(A,B\) 两式:

\[(\sum^k_{i=1}f_i)^2<10^4k+\sum^k_{i=1}f_t^2<10^4k+2\times 10^4 \]

可以得到:

\[\sum^k_{i=1}f_i<100\sqrt{k+2}\le 100\sqrt{n+2} \]

Code
/*
Author: cnyz
----------------
Looking! The blitz loop this planet to search way.
Only my RAILGUN can shoot it. 今すぐ
*/
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;
#define pb push_back
#define fi first
#define se second
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ROF(i,a,b) for(int i=a;i>=b;i--)
const int N=100;
int n,f[N+10];
ll s[N+10],dp[1012],ans;
void solve() {
	scanf("%d",&n);
	FOR(i,1,n) {
		db x;scanf("%lld %lf",&s[i],&x);
		f[i]=(x*100+0.5);
	}
	ans=0;
	FOR(v,1,1010) {
		memset(dp,0,sizeof dp);
		FOR(i,1,n) ROF(j,v,f[i])
			dp[j]=max(dp[j],dp[j-f[i]]+s[i]*(10000-f[i]*(v-f[i])));
		ans=max(ans,dp[v]);
	}
	printf("%.9lf\n",1.0*ans/10000);
}
int main() {
	int t;scanf("%d",&t);
	FOR(i,1,t) solve();
}
posted @ 2022-02-24 20:35  cnyz  阅读(217)  评论(0编辑  收藏  举报