题解

好题!!!

竞赛图就是把一个无向完全图的所有边定向

这道题其实是比较简单的,考试的时候一直没看到删除k个钦定点图是一个DAG的条件,就一直没有思路

我们可以把钦定点设为A集合,其他的点设为B集合

显然,如果A集合有环,那么肯定无解

根据条件,B集合也是DAG

所以A、B集合都是DAG

我们可以考虑一下他们的拓扑序

设A->B的有向边为x类边,B->A的为y类边

对于B集合的一个点u,A集合的所有点都会与它有连边(因为是有向完全图嘛),或为x类边,或为y类边

把这些连边按照它们对应的A集合的点的拓扑序来排序,如:xxyyxyyxyyxxyyx

可以发现如果出现了y类边,再出现x类边,那么必定会构成环u->Ai->Ai+1->...->->Aj->u    (假设Ai对应的y类边,Aj对应x类边)

由于A集合不能被删除,所以我们只好删掉B集合的点u

那么剩下的B集合的点的对应A集合连边类型序列就一定是xxx...xyyyy...y

考虑到这种情况

如果B集合中靠后点的y序列范围与B集合中靠前点的x序列范围有交集

那么它们也会构成环

于是我们可以对于B集合里的每一个数都存一下:从左到右第一次有x变成y的位置

然后我们发现这就是一个经典的最长不下降子序列问题

DP一下就可以了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
inline int gi()
{
	char c;int num=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num;
}
#define N 2005
int fir[N],to[N*N],nxt[N*N],cnt,d[N];
bool flg[N];
int e[N][N];
int a[N],cnta,b[N],cntb;
void adde(int a,int b){to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;d[b]++;}
queue<int> q;
int f[N],cntf,dp[N];
int main()
{
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	int n,k,i,j,u,v,p,x;
	scanf("%d%d",&n,&k);
	for(i=1;i<=n;i++)for(j=1;j<=n;j++)e[i][j]=gi();
	for(i=1;i<=k;i++)scanf("%d",&x),flg[x]=1;
	for(i=1;i<=n;i++)if(flg[i])
		for(j=1;j<=n;j++)if(flg[j])
			if(e[i][j])adde(i,j);
	for(i=1;i<=n;i++)if(flg[i]&&!d[i])q.push(i);
	while(!q.empty()){
		u=q.front();q.pop();a[++cnta]=u;
		for(p=fir[u];p;p=nxt[p])
			if(!(--d[v=to[p]]))q.push(v);
	}
	if(cnta<k){printf("impossible\n");return 0;}
	memset(fir,0,sizeof(fir));cnt=0;
	for(i=1;i<=n;i++)if(!flg[i])
		for(j=1;j<=n;j++)if(!flg[j])
			if(e[i][j])adde(i,j);
	for(i=1;i<=n;i++)if(!flg[i]&&!d[i])q.push(i);
	while(!q.empty()){
		u=q.front();q.pop();b[++cntb]=u;
		for(p=fir[u];p;p=nxt[p])
			if(!(--d[v=to[p]]))q.push(v);
	}
	
	for(i=1;i<=cntb;i++){
		int tmp=cnta+1;bool ff=0;
		for(j=1;j<=cnta;j++){
			if(e[b[i]][a[j]]){
				if(j==1||e[a[j-1]][b[i]]){
					if(tmp==cnta+1)tmp=j;
					else{ff=1;break;}
				}
			}
		}
		if(e[a[cnta]][b[i]]&&tmp!=cnta+1)ff=1;
		if(!ff)f[++cntf]=tmp;
	}
	int mx=0;
	for(i=1;i<=cntf;i++){
		dp[i]=1;
		for(j=1;j<i;j++)
			if(f[j]<=f[i])
				dp[i]=max(dp[j]+1,dp[i]);
		mx=max(mx,dp[i]);
	}
	if(n-k-mx>=k)printf("impossible\n");
	else printf("%d",n-k-mx);
}

 

 

T2用一篇博客单独来讲

 

T3:

 

 

 

题解

这道题在考试的时候本来有一些思路的,但是把大量时间花在了T2上面,所以就没继续深入思考

很明显,最后的序列只包含0、1,我们只需要维护0的位置就可以了

而0的位置是有规律的(就像上面的题解),每减少一下当前的a[i],就会把最近的一个0拉近一步

然后就可以用一个单调栈来维护0的位置就可以了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 20000005
int n,top;
char s[N];
int a[N],stk[N];
int main()
{
	freopen("simulate.in","r",stdin);
	freopen("simulate.out","w",stdout);
	int i,tmp;
	scanf("%s",s+1);
	n=strlen(s+1),stk[++top]=0;
	for(i=1;i<=n;++i){
		int x=s[i]-48+a[i];s[i]='1';
		while(x>1){
			if(stk[top]==i-1){
				x-=2;
				a[i+1]++;
				top=max(1,top-1);
			}
			else if(!stk[top]){
				stk[++top]=1;
				x--;
				a[i+1]++;
			}
			else{
				tmp=min(i-1-stk[top],x-1);
				stk[top]+=tmp;
				x-=tmp;
				a[i+1]+=tmp;
			}
		}
		if(!x)stk[++top]=i;
	}
	while(top)s[stk[top--]]='0';
	puts(s+1);
}