P3825 [NOI2017]游戏
题目链接:https://www.luogu.com.cn/problem/P3825
我太蠢了呀。
题意读错想半天。蠢的跟猪一样。
最后看了题解才晓得题意读错了,猪,你为何这么蠢。
先来个题意知识:
我们考虑一下当d为0的时候:
那么,每个地图最多会有两辆车子。而且每个地图一定是会从中选取一辆车。
那这也就是一个2-SAT问题的模板了。只是说我们要仔细考虑。
如果xi地图为B,则i表示选择A车,i+n表示选择C车
如果地图为C,则i表示选择A车,i+n表示选择B车
如果地图为A,则i表示选择B车,i+n表示选择C车。
考虑建边问题:
对于一个四元组(i,hi,j,hj)
如果str[i]==hi,则表示地图i中,根本不可能选择hi这辆车,那这个直接continue就好。
在上面条件不满足情况下,如果str[j]==hj,则表示,j这个位置一定不可以选择hj这辆车。那么我们可以把i和i+n进行连接,表示如果i这个位置选择了hi,则也一定要选择i’。
在上面条件都不满足情况下,也就是说i这个为位置可以选择hi,j这个位置可以选择hj。
那我肯定要i指向j的位置。同时因为逆否命题,我要j'指向i'这个位置。
然后观察得到d值较小。我们可以直接枚举每个X位置会为什么情况。
理论上,我们需要枚举每个x位置分别为A,B,C 的情况
也就是:
1,x位置为A,则可选车为B和C
2,x位置为B,则可选车为A和C
3,x位置为C,则可选车为A和B
我们可以发现,第一种和第二种其实已经包含了第三种。
所以我们只要枚举两种情况即可。
#include"stdio.h" #include"string.h" #include"vector" #include"algorithm" using namespace std; #define OK printf("\n"); #define exit return 0; const int N = 1e6 * 2 + 1010; const int M = 2 * N; int read() { int f=1,x=0; char ss=getchar(); while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();} while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();} return f*x; } int head[N],ver[M],Next[M],tot;int n,m,num,top,cnt; int dfn[N],low[N],id[110];int limit = 0; int Stack[N],ins[N],c[N]; int s[N]; char str[N]; int X[N],Y[N]; char cx[N],cy[N]; void add(int x,int y){ ver[++ tot] = y; Next[tot] = head[x]; head[x] = tot; } void tarjan(int x){ dfn[x] = low[x] = ++ num; Stack[++ top] = x; ins[x] = 1; for(int i = head[x]; i; i = Next[i]) if(!dfn[ver[i]]) { tarjan(ver[i]); low[x] = min(low[x],low[ver[i]]); } else if(ins[ver[i]]) low[x] = min(low[x],dfn[ver[i]]); if(dfn[x] == low[x]){ cnt ++; int y; do{ y = Stack[top --];ins[y] = 0; c[y] = cnt; } while(x != y); } } void Build_edge() { for(int i = 1; i <= m; i ++){ if(str[X[i]] == cx[i]) continue; if(str[Y[i]] == cy[i]) { if(cx[i] == 'C' || (cx[i] == 'B' && str[X[i]] == 'C')) { add(X[i] + n,X[i]); //printf("%d -> %d\n",X[i] + n,X[i]); } else { add(X[i],X[i] + n); //printf("%d -> %d\n",X[i],X[i] + n); } continue; } int v1 = (cy[i] == 'C' || (cy[i] == 'B' && str[Y[i]] == 'C')) ? 1 : 0; int v2 = (cx[i] == 'C' || (cx[i] == 'B' && str[X[i]] == 'C')) ? 1 : 0; add(X[i] + n * (v2),Y[i] + n * v1); add(Y[i] + n * (v1 ^ 1),X[i] + n * (v2 ^ 1)); // printf("%d -> %d\n",X[i] + n *(v2),Y[i] + n * v1); // printf("%d -> %d\n",Y[i] + n * (v1 ^ 1),X[i] + n * (v2 ^ 1)); } return ; } void init() { num = 0; memset(ins,0,sizeof(ins)); cnt = 0; tot = 0; top = 0; memset(low,0,sizeof(low)); memset(c,0,sizeof(c)); memset(dfn,0,sizeof(dfn));memset(Stack,0,sizeof(Stack)); memset(head,0,sizeof(head)); } void solve(){ int mark = 1; for(int i = 0; i < (1 << limit); i ++){ init();mark = 1; for(int j = 1; j <= limit; j ++){ str[id[j]] = (i & (1 << (j - 1))) ? 'A' : 'B'; } Build_edge(); for(int i = 1; i <= n * 2; i ++){ if(!dfn[i]) tarjan(i); } for(int i = 1; i <= n; i ++) if(c[i] == c[i + n]) {mark = 0; break;} if(mark == 0) continue; for(int i = 1; i <= n; i ++){ if(c[i] < c[i + n]){ if(str[i] == 'A') printf("B"); else printf("A"); } else { if(str[i] == 'C') printf("B"); else printf("C"); } } return ; } printf("-1"); return ; } int main() { int d; scanf("%d%d",&n,&d); scanf("%s",str + 1); for(int i = 1; i <= n; i ++) { if(str[i] == 'x') id[++ limit] = i; else str[i] -= 32; } m = read(); for(int i = 1; i <= m; i ++){ scanf("%d %c %d %c",&X[i],&cx[i],&Y[i],&cy[i]); } solve(); return 0; }
小 L 对游戏有一些特殊的要求,这些要求可以用四元组 (i,hi,j,hj)来描述,表示若在第i场使用型号为hi的车子,则第j场游戏要使用型号为hj的车子。
这句话的意思应该是在第i场选择了hi的时候,则第j场必须选择hj。
但是是建了反向边的呢?也就是说: 若两个都可用,则从i向j连边,表示若选i,则一定选j,同时从j′向i′连边,这里表示若没有选j,则一定没有选i。
为啥没有选hj,则i那个位置不能选hi呢?题目只是说i选择了hi,则j必须选择hj呀。我相信有部分同学会有这个困惑。
解释: