题解
好题!!!
竞赛图就是把一个无向完全图的所有边定向
这道题其实是比较简单的,考试的时候一直没看到删除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);
}