IOI2020 集训队作业 Part 3

[AGC029F] Construction of a tree

[AGC030C] Coloring Torus

[AGC030D] Inversion Sum

[AGC030E] Less than 3

[AGC030F] Permutation and Minimum

把所有数(包括已经确定的)从大往小考虑。那么每一个 \(b\) 是什么就是看它对应的那一对什么时候被填完。

如果一对都已经有数了,没有意义,扔掉。

\(f_{i,j,k}\) 表示考虑了 \(i\)\(2n\) 的数,填了一个数的对中,有 \(j\) 对是人为填了一个数,有 \(k\) 对是天然填了一个数。后两维是有区别的,因为后面那种是每对固定了位置,前面的没有。

注意到由于前面那种可以每对之间任排,所以就暂不考虑前面那种之间的顺序,答案为 \(f_{1,0,0}\times c!\)

边界 \(f_{2n+1,0,0}=1\)

如果是一个人为填进去的数,看看是新开一对,还是与另一个人为填的配,还是与天然填的配。\(f_{i,j,k}=f_{i+1,j-1,k}+f_{i+1,j+1,k}+(k+1)f_{i+1,j,k+1}\)

如果是一个天然填进去的数,同样,注意不能和另一个天然填的配。\(f_{i,j,k}=f_{i+1,j,k-1}+f_{i+1,j+1,k}\)

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

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=333,mod=1000000007;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,f[maxn*2][maxn][maxn],cnt1,cnt2,ans;
bool vis[maxn*2],ind[maxn*2];
int main(){
	n=read();
	FOR(i,1,n){
		int x=read(),y=read();
		if(~x && ~y) vis[x]=vis[y]=true;
		else if(~x) ind[x]=true,cnt2++;
		else if(~y) ind[y]=true,cnt2++;
		else cnt1++;
	}
	f[2*n+1][0][0]=1;
	ROF(i,2*n,1) FOR(j,0,cnt1+cnt2) FOR(k,0,cnt2){
		if(vis[i]) f[i][j][k]=f[i+1][j][k];
		else if(ind[i]){
			if(k) f[i][j][k]=(f[i][j][k]+f[i+1][j][k-1])%mod;
			if(j!=cnt1+cnt2) f[i][j][k]=(f[i][j][k]+f[i+1][j+1][k])%mod;
		}
		else{
			if(j) f[i][j][k]=(f[i][j][k]+f[i+1][j-1][k])%mod;
			if(j!=cnt1+cnt2) f[i][j][k]=(f[i][j][k]+f[i+1][j+1][k])%mod;
			if(k!=cnt2) f[i][j][k]=(f[i][j][k]+1ll*(k+1)*f[i+1][j][k+1])%mod;
		}
	}
	ans=f[1][0][0];
	FOR(i,1,cnt1) ans=1ll*ans*i%mod;
	printf("%d\n",ans);
}

[AGC031D] A Sequence of Permutations

[AGC031E] Snuke the Phantom Thief

[AGC031F] Walk on Graph

[AGC032C] Three Circuits

[AGC032D] Rotation Sort

[AGC032E] Modulo Pairing

[AGC032F] One Third

[AGC033D] Complexity

[AGC033E] Go around a Circle

[AGC033F] Adding Edges

[AGC034D] Manhattan Max Matching

[AGC034E] Complete Compress

[AGC034F] RNG and XOR

[AGC035C] Skolem XOR Tree

[AGC035D] Add and Remove

留下的卡肯定是第一张和最后一张。

最后答案可以表示成 \(\sum a_ik_i\) 的形式。下称 \(k_i\) 为贡献系数。

考虑把整个过程倒过来,一开始只有第一张和最后一张卡(但是权值不是输入给出的那个),然后每次中间长出一张卡(权值也不是输入给出的),两边的权值减掉中间那个卡的权值。

\(f_{l,r,x,y}\) 表示第 \(l\) 张卡和第 \(r\) 张卡相邻,\(k_l=x,k_r=y\),这时的最小值。

答案即为 \(f_{1,n,1,1}\)

边界,\(l+1=r\) 时,\(f_{l,r,x,y}=xa_l+ya_r\)

转移,枚举中间长出的是第 \(i\) 张卡。那么 \(k_i=x+y\)

方程 \(f_{l,r,x,y}=\min\limits_{l+1\le i\le r-1}(f_{l,i,x,x+y}+f_{i,r,x+y,y}-a_i(x+y))\)

虽然看起来状态数很多,但是如果我们固定看第三、四维,实际上只有 \(2^n\) 种,要么往左就肯定是 \((x,x+y)\),要么往右就肯定是 \((x+y,y)\)

此时有时间复杂度 \(O(2^nn^3)\) 带比较小的常数。据说可以继续分析到 \(O(2^nn)\)

由于我比较菜,弄了一堆组合式子算出来个 \(O(3^nn)\),就不拿出来丢人现眼了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef pair<PII,PII> st;
const int maxn=100010;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,a[maxn];
vector<ll> dp[20][20];
ll DP(int l,int r,int x,int y,int t){
	if(l+1==r) return 1ll*a[l]*x+1ll*a[r]*y;
	ll &d=dp[l][r][t];
	if(d) return d;
	ll s=1e18;
	FOR(i,l+1,r-1) s=min(s,DP(l,i,x,x+y,t<<1)+DP(i,r,x+y,y,t<<1|1)-1ll*a[i]*(x+y));
	return d=s;
}
int main(){
	n=read();
	FOR(i,1,n) a[i]=read();
	FOR(i,1,n) FOR(j,i+1,n) dp[i][j].resize(1<<(n-j+i));
	printf("%lld\n",DP(1,n,1,1,1));
}

[AGC035E] Develop

[AGC035F] Two Histograms

[AGC036D] Negative Cycle

[AGC036E] ABC String

[AGC036F] Square Constraints

[AGC037D] Sorting a Grid

为了方便,给每个数染上它最后应该在的那个行的编号的颜色,共 \(n\) 种颜色。

在第三次重排前,一定有每一行已经有了它该有的那些数。

在第二次重排前,一定有每一列都有每一种颜色恰好一个。

所以第一次重排,就是要让每一列都有每种颜色恰好一个。

确定了第一次重排的方案,第二次重排就很简单了,列内按颜色排序即可。

考虑从左往右一列一列地填第一次重排完后的矩阵。在填一列的时候,就是要让每一行能和每一种颜色一一配对。

那么如果一行中有一种颜色,就把它和这种颜色连边。出现多次就连多条边。

网络流求出这个二分图的完美匹配,就能知道这一列怎么填了。把这些匹配边删掉然后填下一列。

关于每次都能找到完美匹配的正确性证明:

Hall 定理:一个两部点数相同的二分图有完美匹配,当且仅当对于左点集的任意一个子集,出边连向的右边点的并集大小大于等于该点集大小。

必要性显然,下证充分性。

考虑它的最大匹配,若存在一个点 \(u\) 没有匹配上,由前提一定有至少一个与它相连的点。

如果这些点中有没被匹配的,那么它们是可以匹配的,与最大匹配矛盾。

否则,设这连向的点有 \(x\) 个,把这 \(x\) 个点对应的匹配点也拎出来。

考虑这些点和 \(u\)\(x+1\) 个点,由前提一定至少有 \(x+1\) 个与它们相连的点。

由于和 \(u\) 相连的点都已经在相连点集中了(只有 \(x\) 个),所以剩下的那些点一定是与除了 \(u\) 的其它 \(x\) 个点相邻。

如果那些点有没被匹配的,那么存在增广路,矛盾。否则继续连,继续……

最后会全部左点都在里面,就只能矛盾了。

用它来证这题就很简单了:在填第 \(i\) 列的时候,左边右边每个点度数都是 \(m-i+1\),所以对于左边大小为 \(s\) 的点集,与它相连的右边点集度数和至少是 \(s(m-i+1)\),大小也肯定至少是 \(s\)

时间复杂度 \(O(nm^2\sqrt{n})\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=222,maxm=22222;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,m,a[maxn][maxn],b[maxn][maxn],c[maxn][maxn],tmp[maxn];
int s,t,q[maxn],h,r,dis[maxn],el,head[maxn],cur[maxn],to[maxm],nxt[maxm],w[maxm];
bool use[maxm];
inline void add(int u,int v,int w_){
	to[++el]=v;nxt[el]=head[u];head[u]=el;w[el]=w_;
	to[++el]=u;nxt[el]=head[v];head[v]=el;w[el]=0;
}
bool bfs(){
	MEM(dis,-1);
	q[h=r=1]=s;
	dis[s]=0;
	while(h<=r){
		int u=q[h++];
		for(int i=head[u];i;i=nxt[i]) if(w[i]){
			int v=to[i];
			if(dis[v]==-1) dis[v]=dis[u]+1,q[++r]=v;
		}
	}
	return ~dis[t];
}
int dfs(int u,int minres){
	if(u==t || !minres) return minres;
	int f,rtf=0;
	for(int &i=cur[u];i;i=nxt[i]){
		int v=to[i];
		if(dis[v]==dis[u]+1 && (f=dfs(v,min(minres,w[i])))){
			minres-=f;rtf+=f;
			w[i]-=f;w[i^1]+=f;
			if(!minres) break;
		}
	}
	return rtf;
}
int main(){
	n=read();m=read();
	FOR(i,1,n) FOR(j,1,m) a[i][j]=read();
	s=2*n+1;t=2*n+2;
	FOR(i,1,m){
		FOR(i,1,2*n+2) head[i]=0;
		FOR(i,1,el) to[i]=nxt[i]=w[i]=0;
		el=1;
		FOR(i,1,n) add(s,i,1),add(i+n,t,1);
		FOR(i,1,n) FOR(j,1,m) if(!use[a[i][j]]) add(i,(a[i][j]+m-1)/m+n,1);
		int f=0;
		while(bfs()){
			FOR(i,1,2*n+2) cur[i]=head[i];
			f+=dfs(s,1e9);
		}
		assert(f==n);
		FOR(j,1,n) for(int e=head[j];e;e=nxt[e]) if(!w[e]){
			int tp=to[e]-n;
			FOR(k,1,m) if((a[j][k]+m-1)/m==tp && !use[a[j][k]]){
				use[b[j][i]=a[j][k]]=true;break;
			}
		}
	}
	FOR(i,1,n){
		FOR(j,1,m) printf("%d ",b[i][j]);
		puts("");
	}
	FOR(j,1,m){
		FOR(i,1,n) tmp[i]=b[i][j];
		sort(tmp+1,tmp+n+1);
		FOR(i,1,n) c[i][j]=tmp[i];
	}
	FOR(i,1,n){
		FOR(j,1,m) printf("%d ",c[i][j]);
		puts("");
	}
}

[AGC037E] Reversing and Concatenating

[AGC037F] Counting of Subarrays

[AGC038E] Gachapon

[AGC038F] Two Permutations

[AGC039D] Incenters

[AGC039E] Pairing Points

以下令 \(n\) 为真正的总点数。

枚举 \(1\) 和哪个点配,设为 \(i\)

现在是要:在 \([2,i)\) 中选一些点 \(p_1<p_2<\dots<p_x\),和 \((i,n]\) 中选一些点 \(q_1>q_2>\dots>q_x\)\(p_i\)\(q_i\) 连线,对于剩下那些没被选到的点,之间连,的合法方案数。

令这个为 \(f_{l,i,r}\),要求即为所有 \(f_{2,i,n}\) 的和。边界 \(l=i=r\) 方案数是 \(1\)

枚举 \(p_1\)\(q_1\),设为 \(j\)\(k\)

注意到 \((j,i)\) 中连出去的边中,只可能是 \([l,j)\)\((i,k)\)。且应该存在一个 \(p\) 使得 \(p\) 和之前的都连向 \([l,j)\)\(p+1\) 和之后的都连向 \((i,k)\)

另一边同理,设分界点为 \(q\)

注意到 \([l,j)\)\((j,p]\) 连出的线不可能离开这一段,所以这里的方案数是 \(f_{l,j,p}\)

另一边同理,\(f_{q,k,r}\)

对于下面的 \([p+1,i)\)\((i,q-1]\) 也是一样,\(f_{p+1,i,q-1}\)

所以转移方程是 \(f_{l,i,r}+=\sum f_{p+1,i,q-1}f_{l,j,p}f_{q,k,r}[a_{j,k}=1]\)

转移顺序是按 \(r-l+1\) 从小到大枚举。

时间复杂度 \(O(n^7)\),虽然 \(n\le 40\),由于常数极其优秀已经可以通过。

(看转移条件大概就能看出来了:\(l\le j\le p<i<q\le k\le r\)\(r-l+1\) 为奇数,\(a_{j,k}=1\)

但是这个是可以继续优化的。

仍然按 \(r-l+1\) 从小到大枚举。然后里面先枚举 \(p,q\)

注意到如果枚举了 \(i\),那么 \(f_{l,i,r}+=f_{p+1,i,q-1}\sum f_{l,j,p}f_{q,k,r}[a_{j,k}=1]\),后面和 \(i\) 无关。

设这个为 \(s\)。考虑如何快速计算 \(s\)

\(sum_{l,k,p}=\sum f_{l,j,p}[a_{j,k}=1]\)。那么 \(s=\sum sum_{l,k,p}f_{q,k,r}\)

在适当的时候更新 \(sum\) 即可。

时间复杂度 \(O(n^5)\),常数依然小的可怕。经过测试如果改成取模,能跑 \(n\le 100\)(这里的 \(n\) 是新的 \(n\))。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=44;
#define MP make_pair
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int n,a[maxn][maxn];
char s[maxn];
ll f[maxn][maxn][maxn],fs[maxn][maxn][maxn],ans;
int main(){
	n=read()*2;
	FOR(i,1,n){
		scanf("%s",s+1);
		FOR(j,1,n) a[i][j]=s[j]-'0';
	}
	if(n==2) return printf("%d\n",a[1][2]),0;
	FOR(i,2,n){
		f[i][i][i]=1;
		FOR(j,2,n) if(a[i][j]) fs[i][j][i]+=f[i][i][i];
	}
	FOR(len,3,n-1) if(len%2==1) FOR(l,2,n-len+1){
		int r=l+len-1;
		FOR(i,l,r) FOR(j,2,n) if(a[i][j]) fs[l][j][r]-=f[l][i][r];
		FOR(p,l,r) FOR(q,p+1,r){
			ll s=0;
			FOR(k,q,r) s+=fs[l][k][p]*f[q][k][r];
			FOR(i,p+1,q-1) f[l][i][r]+=s*f[p+1][i][q-1];
		}
		FOR(i,l,r) FOR(j,2,n) if(a[i][j]) fs[l][j][r]+=f[l][i][r];
	}
	FOR(i,2,n) if(a[1][i]) ans+=f[2][i][n];
	printf("%lld\n",ans);
}

[AGC039F] Min Product Sum

[ARC089D] ColoringBalls

[ARC091D] Strange Nim

[ARC092D] Two Faced Edges

[ARC093C] Bichrome Spanning Tree

[ARC093D] Dark Horse

[ARC095D] Permutation Tree

[ARC096C] Everything on It

[ARC096D] Sweet Alchemy

[ARC097D] Monochrome Cat

[ARC098D] Donation

[ARC099D] Eating Symbols Hard

[ARC100D] Colorful Sequences

[ARC101C] Ribbons on Tree

[ARC101D] Robots and Exits

[ARC102D] Revenge of BBuBBBlesort!

[ARC103B] Robot Arms

[ARC103D] Distance Sums

posted @ 2020-04-04 14:54  ATS_nantf  阅读(392)  评论(0编辑  收藏  举报