抽象模型+阿巴细节题

Codeforces 题面传送门 & 洛谷题面传送门

首先假设我们已经填好了所有问号处的值怎样判断是否存在一个合法的构造方案,显然对于一种方案能够构造出合法的基环内向森林当且仅当:

  1. \(\forall x,(0,x)\) 的个数为 \(x\) 的倍数,否则不能构成若干个长度为 \(x\) 的环。
  2. 设 \(mx_y=\max\{x|(x,y)\in S\}\),其中 \(S\) 为所有二元组构成的集合,那么必然有二元组 \((0,y),(1,y),(2,y),\cdots,(mx_y,y)\in S\),否则感性理解一下可知必然存在某个二元组 \((x,y)\) 没地方连。

具体构造如下:

  • 对于所有形如 \((0,x)\) 的二元组,我们将它们每 \(x\) 个分一组,然后每组内单独成环。
  • 对于所有形如 \((x,y)(x>1)\) 的二元组,我们随便找一个 \((x-1,y)\) 连过去。

合法性显然。因此我们如果能够构造出一个合法的替换问号的方案,那么这题就做完了。那么怎么构造出合法的替换问号的方案呢?不难发现,这个模型可以转化为,你有若干个二元组 \((x,y)\),每个二元组还需要若干个,你还有若干个带问号的二元组,每个带问号的二元组可以通过将问号替换为数对某个二元组的数量产生最多 \(1\) 的贡献,问分配方案。看到这样的设问方式咱可以轻松想到二分图匹配,具体来说你将所有带问号的二元组放在左边,从源点向它们连容量 \(1\) 的边,需要的二元组放在右边,向汇点连边,容量为需要的二元组的个数,对于可以替换的部分就在中间连一条容量为 \(1\) 的边,跑最大流即可。

接下来考虑怎样具体实现。由于我们的构造方案需要满足以上两个性质,因此考虑将它们分开来考虑。首先 \((0,x)\) 是 \(x\) 的倍数这件事是好办的,如果我们记 \(c_{i,j}\) 表示二元组 \((i,j)\) 的个数,那么 \((0,x)\) 的个数至少是 \(\lceil\dfrac{c_{0,x}}{x}\rceil·x\) 个,当然有一个细节就是如果存在第二维为 \(x\) 的二元组,但不存在 \((0,x)\),此时 \((0,x)\) 的个数也应至少有 \(x\) 个,而不是上式算出来的 \(0\) 个,我们就将它看作一个二元组放在右部,连一条它到汇点,容量 \(\lceil\dfrac{c_{0,x}}{x}\rceil·x-c_{0,x}\) 的边表示还需要这么多个二元组 \((0,x)\)。比较棘手的是第二个限制,由于形如 \((x,?)\) 这样的二元组的存在,我们并不知道它对哪个位置的 \(mx\) 产生了影响,而暴力枚举费时费力,因此我们的做法貌似假掉了。但是有一个非常 trivial 的性质,就是在所有 \((x,?)\) 的二元组中,其实我们只用关心 \(x\) 最大的二元组即可,假设这样的二元组中 \(x\) 最大的二元组最终被替换为了 \((x,y)\),那么其他这样的二元组都把问号替换为 \(y\) 一定是合法的,因此考虑枚举这 \(x\) 最大的二元组替换为了什么,这样我们即可知道每个 \(y\) 的 \(mx_y\),这下子就好办了呗,对于所有 \(x\in[1,mx_y]\),如果 \(c_{x,y}=0\),就将 \((x,y)\) 看作一个需要的二元组放在右部,并向汇点连容量 \(1\) 的边。然后对于所有带问号的二元组,向可以替换的二元组连边并跑最大流即可。

最后有一些二元组的问号是没有替换的,对于 \((?,?)\) 的二元组我们直接将它替换为 \((0,1)\) 即可,\((?,x)\) 直接把 \(?\) 换位 \(1\) 即可,\((x,?)\) 稍微要分下类,如果 \(x=0\) 那 \(?\) 设为 \(1\) 即可,\(x\ne 0\) 就找到一个 \(mx_y\ge x\) 的 \(y\) 然后将 \(?\) 替换为 \(y\) 即可。

算下复杂度,由于最多只有 \(n\) 个二元组能够产生贡献,因此如果需要的二元组个数 \(>n\) 就直接返回不可行即可。因此点数是线性的,边数是平方的,再加上外层枚举的 \(n\),总复杂度 \(n^3\),可以通过此题。

细节有点多,一不小心又码了 \(190\) 行。

const int MAXN=300;
const int MAXV=1000;
const int MAXE=2e5;
const int INF=0x3f3f3f3f; 
int n,m,a[MAXN+5],b[MAXN+5];
int parse(string s){
	if(s=="?") return -1;int x=0;
	for(int i=0;i<s.size();i++) x=x*10+s[i]-'0';
	return x;
}
int cnt[MAXN+5][MAXN+5],mx[MAXN+5],id[MAXN+5][MAXN+5],has[MAXN+5];
int S=999,T=1000,hd[MAXV+5],to[MAXE+5],nxt[MAXE+5],cap[MAXE+5],ec=1;
void adde(int u,int v,int f){
//	printf("%d %d %d\n",u,v,f);
	to[++ec]=v;cap[ec]=f;nxt[ec]=hd[u];hd[u]=ec;
	to[++ec]=u;cap[ec]=0;nxt[ec]=hd[v];hd[v]=ec;
} int dep[MAXV+5],now[MAXV+5];
bool getdep(){
	memset(dep,-1,sizeof(dep));dep[S]=0;
	queue<int> q;q.push(S);now[S]=hd[S];
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e],z=cap[e];
			if(z&&!~dep[y]){
				dep[y]=dep[x]+1;
				now[y]=hd[y];q.push(y);
			}
		}
	} return ~dep[T];
}
int getflow(int x,int f){
	if(x==T) return f;int ret=0;
	for(int &e=now[x];e;e=nxt[e]){
		int y=to[e],z=cap[e];
		if(dep[y]==dep[x]+1&&z){
			int w=getflow(y,min(f-ret,z));
			cap[e]-=w;cap[e^1]+=w;ret+=w;
			if(ret==f) return ret;
		}
	} return ret;
}
int dinic(){
	int res=0;
	while(getdep()) res+=getflow(S,INF);
	return res;
}
bool check(){
	memset(cnt,0,sizeof(cnt));memset(mx,0,sizeof(mx));
	memset(has,0,sizeof(has));memset(id,0,sizeof(id));
	memset(hd,0,sizeof(hd));ec=1;int sum=0;m=0;
	for(int i=1;i<=n;i++) if(~a[i]&&~b[i]){
		cnt[a[i]][b[i]]++;
		chkmax(mx[b[i]],a[i]);
	} for(int i=1;i<=n;i++) adde(S,i,1);
	for(int i=1;i<=n;i++) if(~b[i]) has[b[i]]=1;
//	for(int i=1;i<=n;i++) printf("%d%c",mx[i]," \n"[i==n]);
	for(int i=1;i<=n;i++) if(mx[i]||cnt[0][i]||has[i]){
		int need=(cnt[0][i]+i-1)/i*i;
		if(!need) need=i;
		if(need^cnt[0][i]){
			if((id[0][i]=++m)>n) return 0;
//			printf("id[0][%d]=%d\n",i,m);
			adde(m+n,T,need-cnt[0][i]);
			sum+=need-cnt[0][i];
//			printf("%d %d\n",i,need-cnt[0][i]);
		}
		for(int j=1;j<=mx[i];j++) if(!cnt[j][i]){
			if((id[j][i]=++m)>n) return 0;
//			printf("id[%d][%d]=%d\n",j,i,m);
			adde(m+n,T,1);sum++;
		}
	}
	for(int i=1;i<=n;i++){
		if(~a[i]&&~b[i]) continue;
		else if(~a[i]){
//			printf("%d\n",i);
			for(int j=1;j<=n;j++) if(id[a[i]][j])
				adde(i,id[a[i]][j]+n,1);
		} else if(~b[i]){
//			printf("%d\n",i);
			for(int j=0;j<=n;j++) if(id[j][b[i]])
				adde(i,id[j][b[i]]+n,1);
		} else {
			for(int j=1;j<=m;j++) adde(i,j+n,1);
//			printf("%d\n",i);
		}
	} return (dinic()==sum);
}
void work(){
	for(int i=0;i<=n;i++) for(int j=1;j<=n;j++){
		if(id[i][j]){
			int k=id[i][j];
//			printf("%d %d %d\n",i,j,k);
			for(int e=hd[k+n];e;e=nxt[e]){
//				printf("%d\n",e);
				if(to[e]!=T&&cap[e]){
					int x=to[e];
//					printf("x=%d %d %d\n",x,a[x],b[x]);
					assert(!~a[x]||a[x]==i);
					assert(!~b[x]||b[x]==j);
					a[x]=i;b[x]=j;
//					printf("%d %d\n",a[x],b[x]);
				}
			}
		}
	}
	for(int i=1;i<=n;i++) if(!~a[i]||!~b[i]){
		if(!~a[i]&&!~b[i]) a[i]=0,b[i]=1;
		else if(!~a[i]) a[i]=1;
		else{
			if(!a[i]) b[i]=1;
			else{
				for(int j=1;j<=n;j++) if(mx[j]>=a[i])
					b[i]=j;
			}
		}
	}
//	for(int i=1;i<=n;i++) printf("%d %d\n",a[i],b[i]);
}
vector<int> vec[MAXN+5];
int ans[MAXN+5];
void makegraph(){
	for(int i=1;i<=n;i++) if(!a[i]) vec[b[i]].pb(i);
	for(int i=1;i<=n;i++) if(!vec[i].empty()){
		assert(vec[i].size()%i==0);
		for(int j=0;j<vec[i].size();j+=i){
			for(int k=j;k<j+i-1;k++) ans[vec[i][k]]=vec[i][k+1];
			ans[vec[i][j+i-1]]=vec[i][j];
		}
	}
	for(int i=1;i<=n;i++) if(a[i])
		for(int j=1;j<=n;j++) if(a[j]==a[i]-1&&b[j]==b[i]){
			ans[i]=j;
		}
	for(int i=1;i<=n;i++) printf("%d%c",ans[i]," \n"[i==n]);
}
int main(){
	scanf("%d",&n);int mx=0;
	for(int i=1;i<=n;i++){
		string s,t;cin>>s>>t;
		a[i]=parse(s);b[i]=parse(t);
		if(~a[i]&&!~b[i])
			if(a[i]>a[mx]) mx=i;
	}
	for(int i=1;i<=n;i++){
//		printf("check %d\n",i);
		if(mx) b[mx]=i;
		if(check()) return work(),makegraph(),0;
		if(mx) b[mx]=-1;
	} puts("-1");
	return 0;
}