IOI2020集训队作业-14 (CF611H, AGC036D, AGC027E)

A - CF611H New Year and Forgotten Tree

Sol

首先位数相同的点之间的边随便连。此时每两种位数的点之间的边数和每种位数的连通块数。

如果有解则一定存在一种方案,是将每种位数拿一个点出来,连成一个生成树,然后把其它的点连到生成树上的点。

爆搜生成树的形态,然后用网络流求匹配判断是否存在方案。

Code

#include 
#include 
#include 
#include 
#include 
#include 
#define PII pair
#define PB push_back
#define MP make_pair
#define fir first
#define sec second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
int S,T,ncnt;
namespace Flow {
	const int N=210;
	int head[N],cur[N],dep[N];
	struct ed { int to,next,f; };
	vector<ed> e;
	void init() { memset(head,-1,sizeof(head)); e.clear(); }
	void ad(int x,int y,int f) {
		e.PB((ed){y,head[x],f}); head[x]=e.size()-1;
		e.PB((ed){x,head[y],0}); head[y]=e.size()-1;
	}
	queue<int> que;
	bool bfs() {
		for(int i=1;i<=ncnt;++i) dep[i]=-1,cur[i]=head[i];
		while(!que.empty()) que.pop();
		que.push(S),dep[S]=0;
		while(!que.empty()) {
			int u=que.front(); que.pop();
			if(u==T) return 1;
			for(int k=head[u];~k;k=e[k].next) if(e[k].f) {
				int v=e[k].to;
				if(dep[v]==-1) {
					dep[v]=dep[u]+1;
					que.push(v);
				}
			}
		}
		return 0;
	}
	int dfs(int u,int f) {
		if(u==T||!f) return f; int tmp,ret=0;
		for(int &k=cur[u];~k;k=e[k].next) if(e[k].f) {
			int v=e[k].to;
			if(dep[v]==dep[u]+1&&(tmp=dfs(v,min(e[k].f,f)))) {
				e[k].f-=tmp,e[k^1].f+=tmp;
				f-=tmp,ret+=tmp;
				if(!f) break;
			}
		}
		return ret;
	}
	int work() { int ans=0; while(bfs()) ans+=dfs(S,1e9); return ans; }
	void get(int mp[][210]) {
		for(int i=1;i<=ncnt;++i)
			for(int k=head[i];~k;k=e[k].next)
				mp[i][e[k].to]=e[k].f;
	}
}
int m,n;
int E[10][10],num[10];
int id1[10][10],id2[10];
vector<PII> ansE;
bool sol() {
	Flow::init();
	for(int i=0;i<m;++i)
		for(int j=0;j<i;++j)
			if(E[i][j]) {
				Flow::ad(S,id1[i][j],E[i][j]);
				Flow::ad(id1[i][j],id2[i],E[i][j]);
				Flow::ad(id1[i][j],id2[j],E[i][j]);
			}
	for(int i=0;i<m;++i) Flow::ad(id2[i],T,num[i]-1);
	return Flow::work()==n-m-ansE.size();
}
int pru[10];
int vis[10],buc[10];
int G[10][10];
int flow[210][210],pcnt[10][10];
vector<int> node[10];
void getpru() {
	memcpy(E,G,sizeof(G));
	for(int i=0;i<m;++i) vis[i]=buc[i]=0;
	for(int i=0;i<m-2;++i) buc[pru[i]]++;
	for(int i=0;i<m-2;++i) {
		int p=0;
		while(buc[p]||vis[p]) p++;
		E[p][pru[i]]--,E[pru[i]][p]--;
		vis[p]=1;
		buc[pru[i]]--;
	}
	for(int i=0;i<m;++i) if(!vis[i])
		for(int j=i+1;j<m;++j) if(!vis[j])
			E[i][j]--,E[j][i]--,vis[i]=vis[j]=1;
	for(int i=0;i<m;++i) for(int j=0;j<m;++j) if(E[i][j]<0) return;
	if(sol()) {
		for(int i=0;i<ansE.size();++i) printf("%d %d\n",ansE[i].fir,ansE[i].sec);
		for(int i=0;i<m;++i)
			for(int j=0;j<i;++j)
				if(E[i][j]<G[i][j]) printf("%d %d\n",node[i][0],node[j][0]);
		for(int i=0;i<m;++i) for(int j=0;j<m;++j) pcnt[i][j]=pcnt[j][i]=0;
		Flow::get(flow);
		for(int i=0;i<m;++i)
			for(int j=0;j<i;++j) if(E[i][j]) {
				pcnt[i][j]=flow[id2[i]][id1[i][j]];
				pcnt[j][i]=flow[id2[j]][id1[i][j]];
			}
		for(int i=0;i<m;++i)
			for(int k=1;k<node[i].size();++k)
				for(int j=0;j<m;++j) if(pcnt[i][j]) {
					printf("%d %d\n",node[i][k],node[j][0]);
					pcnt[i][j]--;
					break;
				}
		exit(0);
	}
}
void dfs(int cur) {
	if(cur>=m-2) return getpru();
	for(int i=0;i<m;++i) pru[cur]=i,dfs(cur+1);
}
int dig[200010];
char str1[20],str2[20];
int main() {
	rd(n);
	for(int i=1;i<=n;++i) dig[i]=dig[i/10]+1,node[dig[i]-1].PB(i);
	m=dig[n];
	int fail_flg=0;
	for(int i=1;i<n;++i) {
		scanf("%s%s",str1,str2);
		int x=strlen(str1),y=strlen(str2);
		x--,y--;
		if(x==y) {
			if(node[x].size()<=1) fail_flg=1;
			int u=node[x].back(); node[x].pop_back();
			int v=node[x].back();
			ansE.PB(MP(u,v));
		}
		else G[x][y]++,G[y][x]++;
	}
	if(fail_flg) {
		printf("-1");
		return 0;
	}
	for(int i=0;i<m;++i) num[i]=node[i].size();
	S=++ncnt,T=++ncnt;
	for(int i=0;i<m;++i) for(int j=0;j<i;++j) id1[i][j]=++ncnt;
	for(int i=0;i<m;++i) id2[i]=++ncnt;
	dfs(0);
	printf("-1");
	return 0;
}

B - AGC036D Negative Cycle

Sol

d i d_i di表示从第一个点出发到 i i i的最短路。不存在负环等价于我们能够给 d i d_i di赋一个实数值,使得对于任意一条边 e i , j e_{i,j} ei,j,我们都有 d j ≤ d i + e i , j d_j \le d_i + e_{i,j} djdi+ei,j

不存在负环的时候,显然会有$d_i \le d_{i+1} 。 我 们 不 妨 考 虑 确 定 。我们不妨考虑确定 q_i = d_{i+1}-d_i$的值。

考虑一条边 i → j ( i < j ) i\to j(i< j) ij(i<j),它的限制相当于是 d j ≤ d i − 1 d_j \le d_i - 1 djdi1,即 q j − 1 + q j − 2 + ⋯ q i ≥ 1 q_{j-1} +q_{j-2} + \cdots q_i \ge 1 qj1+qj2+qi1。而对于一条边 j → i ( i < j ) j\to i(i< j) ji(i<j),它的限制相当于是 d i ≤ d j + 1 d_i \le d_j+1 didj+1,即 q j − 1 + q j − 2 + ⋯ q i ≤ 1 q_{j-1} +q_{j-2} + \cdots q_i \le 1 qj1+qj2+qi1。发现我们实际上只需要考虑用 0 , 1 0,1 0,1去填这个 q i q_i qi数组,效果和用其他数字填是等价的。计算哪些边必须删去的时候,我们只需要关心为 1 1 1的数字的位置,可以考虑 d p dp dp,在状态中记录下最近的两个 1 1 1的位置,状态数 O ( n 2 ) O(n^2) O(n2),转移 O ( n ) O(n) O(n),总复杂度 O ( n 3 ) O(n^3) O(n3)

Code

#include 
#include 
#include 
#include 
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T> inline void cmin(T &x,T y) { x=min(x,y); }
const int N=510;
ll a[N][N],b[N][N];
ll f[N][N];
int n;
void getsum(ll a[][N]) {
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
}
ll query(ll a[][N],int x1,int x2,int y1,int y2) {
	if(x2<x1||y2<y1) return 0;
	x1--,y1--;
	return a[x2][y2]-a[x1][y2]-a[x2][y1]+a[x1][y1];
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) {
			if(i==j) continue;
			int x; rd(x);
			if(i<j) a[i][j]=x;
			if(i>j) b[i][j]=x;
		}
	getsum(a),getsum(b);
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;++i)
		f[0][i]=query(a,1,i-1,2,i);
	for(int i=0;i<=n-1;++i)
		for(int j=i+1;j<=n-1;++j)
			for(int k=j+1;k<=n-1;++k)
				f[j][k]=min(f[j][k],f[i][j]+query(a,j+1,k-1,j+2,k)+query(b,j+1,k,1,i));
	ll ans=query(a,1,n-1,2,n);
	for(int i=0;i<=n-1;++i)
		for(int j=i+1;j<=n-1;++j) 
			ans=min(ans,f[i][j]+query(a,j+1,n-1,j+2,n)+query(b,j+1,n,1,i));
	printf("%lld",ans);
	return 0;
}

C - AGC027E ABBreviate

Sol

a 1 1 1b 2 2 2,则 s s s可以通过若干次操作后变成字符 c c c,当且仅当 s = c s=c s=c或者 s s s中包含两个相邻且相同的字符并且 s s s中所有字符的和模 3 3 3等于 c c c

必要性显然。充分性可以考虑 s s s必然可以进行操作,并且操作完之后得到的串也一定可以进行操作,直到 ∣ s ∣ = 1 |s|=1 s=1

下面考虑如何判断能否通过对 s s s进行若干次操作得到 t t t:如果 s = t s=t s=t则可以;如果 ∣ s ∣ > ∣ t ∣ |s|>|t| s>t s s s中相邻的字符都不相同显然不可以;将 s s s尽可能短的、且能够得到 t t t的第一个字符的前缀删去,然后对 s s s剩下的部分考虑 t t t的剩下的字符,最后匹配完 t t t的所有字符之后, s s s刚好用完或者剩下一个所有字符的和模 3 3 3 0 0 0的后缀,则可以。

必要性证明:如果中间有一段划分包含了字符和为 0 0 0的后缀且除了这个后缀之外还包含相邻且相同的字符,则可以把这个后缀分到下一段去,所以让划分点尽量靠前一定不会更劣。

充分性证明:最后剩下的一段可以放到划分的最后一段去;如果最后一段恰只有一个字符并且和后面字符和为 0 0 0的段拼成了ababa,那么我们可以把abab分到这一段前面那段去,变成...abab|a,直到abab被分到的那段有相邻且相同的字符。

然后利用以上性质进行 d p dp dp即可,复杂度 O ( ∣ S ∣ ) O(|S|) O(S)

Code

#include 
#include 
#include 
#include 
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1e5+10,mod=1e9+7;
inline void Add(int &x,int y) { x+=y; if(x>=mod) x-=mod; }
inline void Dec(int &x,int y) { x-=y; if(x<0) x+=mod; }
char S[N];
int s[N],f[N],sum[N],n;
int lb[N],rb[N];
int main() {
	scanf("%s",S+1); n=strlen(S+1);
	int flg=0;
	for(int i=1;i<n;++i) flg|=S[i]==S[i+1];
	if(!flg) {
		printf("1\n");
		return 0;
	}
	for(int i=1;i<=n;++i) s[i]=s[i-1]+(S[i]-'a'+1);
	int last[3]={0,-1,-1};
	int lst=-1;
	for(int i=1;i<=n;++i) {
		if(i>1&&S[i-1]==S[i]) lst=i-2;
		rb[i]=lst;
		lb[i]=last[s[i]%3];
		last[s[i]%3]=i;
	}
	f[0]=sum[0]=1;
	for(int i=1;i<=n;++i) {
		Add(f[i],f[i-1]);
		if(lb[i]<rb[i]) {
			Add(f[i],sum[rb[i]]);
			if(lb[i]>-1) Dec(f[i],sum[lb[i]]);
		}
		sum[i]=(sum[i-1]+f[i])%mod;
	}
	int ans=0;
	for(int i=1;i<=n;++i) if((s[n]-s[i])%3==0) Add(ans,f[i]);
	printf("%d",ans);
	return 0;
}
s[i]%3]=i;
	}
	f[0]=sum[0]=1;
	for(int i=1;i<=n;++i) {
		Add(f[i],f[i-1]);
		if(lb[i]<rb[i]) {
			Add(f[i],sum[rb[i]]);
			if(lb[i]>-1) Dec(f[i],sum[lb[i]]);
		}
		sum[i]=(sum[i-1]+f[i])%mod;
	}
	int ans=0;
	for(int i=1;i<=n;++i) if((s[n]-s[i])%3==0) Add(ans,f[i]);
	printf("%d",ans);
	return 0;
}

你可能感兴趣的:(IOI2020集训队作业-14 (CF611H, AGC036D, AGC027E))