1. 程式人生 > 其它 >[NOI2017] 遊戲

[NOI2017] 遊戲

小丑竟是我自己。

前言

這道題把U群搞得很熱鬧,順便教育了一波自以為卡常的菜逼(我)。

題目

洛谷(普通版)

UOJ(煉獄版)

講解

普通版

我們發現雖然總共有 3 種車可以選,但是每個地圖有 1 種車不能選,所以排除掉離譜的 3-SAT 之後,還是一個比較簡單的 2-SAT 板題。

一個比較 naive 的想法是列舉 x 處選什麼車,這樣複雜度是 \(O(3^dn)\) 的,應該在哪裡都過不了,說不定能過原始資料。

但實際上我們只需要列舉 x 處選什麼地圖,根據鴿巢原理,我們發現任意兩個地圖就可以包含三種車,所以複雜度變成 \(O(2^dn)\),普通版可過。

煉獄版

WARNING:前方高能,非戰鬥人員請迅速撤離!

但是普通版的程式碼交到 UOJ 上是過不了的,我的程式碼會 T 在 extest12 上,而且我們一個機房都過不了。

於是我就去 U 群裡面問
EI 表示是卡常(此時我以為正解就是c=2)
然後事情就不對勁起來了
其中混進了一隻萌萌的兔(蛙)隊
隨後 $哥哥 給出做法
最後一個我不認識的大佬給出補充

大家都看懂了把,那我就不講了,所以複雜度就是 \(O(1.5^dn)\)

程式碼

普通版
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 50005 << 1;
int n,m;
char c[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

char gc(){
	char c = getchar();
	while(c > 'D' || c < 'A') c = getchar();
	return c;
}

vector<int> arb;
int head[MAXN],tot;
struct edge{
	int v,nxt;
}e[MAXN<<1];
void Add_Edge(int u,int v){
	e[++tot] = edge{v,head[u]};
	head[u] = tot;
}

int cs[MAXN][2],E[MAXN][4];
void Get(int i,char s){
	if(s == 'a') cs[i][0] = 1,cs[i][1] = 2;
	else if(s == 'b') cs[i][0] = 0,cs[i][1] = 2;
	else if(s == 'c') cs[i][0] = 0,cs[i][1] = 1;
}
int Find(int x,int s){
	if(cs[x][0] == s) return 0;
	else if(cs[x][1] == s) return 1;
	return 2;
}
int dfn[MAXN],dfntot,bl[MAXN],qlt,low[MAXN],st[MAXN],tl;
bool ins[MAXN];
void Tarjan(int x){
	ins[x] = 1; st[++tl] = x;
	dfn[x] = low[x] = ++dfntot;
	for(int i = head[x],v; i ;i = e[i].nxt){
		v = e[i].v;
		if(!dfn[v]) Tarjan(v),low[x] = Min(low[x],low[v]);
		else if(ins[v]) low[x] = Min(low[x],dfn[v]);
	}
	if(dfn[x] == low[x]){
		int v; bl[x] = ++qlt;
		do{
			bl[v = st[tl--]] = qlt;
			ins[v] = 0;
		}while(v ^ x);
	}
}
void solve(){
	for(int i = (n<<1);i >= 1;-- i) dfn[i] = head[i] = bl[i] = ins[i] = 0;
	tl = qlt = tot = dfntot = 0;
	for(int i = 1,A,B;i <= m;++ i){
		if((A = Find(E[i][0],E[i][1])) > 1) continue;
		B = Find(E[i][2],E[i][3]);
		if(B > 1) Add_Edge(E[i][0]+n*A,E[i][0]+n*(A^1));//can't be chosen
		else Add_Edge(E[i][0]+n*A,E[i][2]+n*B),Add_Edge(E[i][2]+n*(B^1),E[i][0]+n*(A^1));
	}
	for(int i = 1;i <= (n<<1);++ i) if(!dfn[i]) Tarjan(i);
	for(int i = 1;i <= n;++ i) if(bl[i] == bl[i+n]) return;
	for(int i = 1;i <= n;++ i) putchar('A'+cs[i][bl[i+n] < bl[i]]);
	exit(0);
}
void dfs(int x){
	if(x == arb.size()){
		solve();
		return;
	}
	for(char s = 'a';s < 'c';++ s) {
		Get(arb[x],s);
		dfs(x+1);
	}
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); Read();
	scanf("%s",c+1);
	for(int i = 1;i <= n;++ i)
		if(c[i] == 'x') arb.emplace_back(i);
		else Get(i,c[i]);
	m = Read();
	for(int i = 1;i <= m;++ i) E[i][0] = Read(),E[i][1] = gc()-'A',E[i][2] = Read(),E[i][3] = gc()-'A';
	dfs(0);
	Put(-1,'\n');
	return 0;
}
煉獄版?
//12252024832524
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 50005 << 1;
int n,m,D;
char c[MAXN];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

mt19937 ee(time(0));

char gc(){
	char c = getchar();
	while(c > 'D' || c < 'A') c = getchar();
	return c;
}

vector<int> arb;
int head[MAXN],tot;
struct edge{
	int v,nxt;
}e[MAXN<<1];
void Add_Edge(int u,int v){
	e[++tot] = edge{v,head[u]};
	head[u] = tot;
}

int cs[MAXN][2],E[MAXN][4];
void Get(int i,char s){
	if(s == 'a') cs[i][0] = 1,cs[i][1] = 2;
	else if(s == 'b') cs[i][0] = 0,cs[i][1] = 2;
	else if(s == 'c') cs[i][0] = 0,cs[i][1] = 1;
}
int Find(int x,int s){
	if(cs[x][0] == s) return 0;
	else if(cs[x][1] == s) return 1;
	return 2;
}
int dfn[MAXN],dfntot,bl[MAXN],qlt,low[MAXN],st[MAXN],tl;
bool ins[MAXN];
void Tarjan(int x){
	ins[x] = 1; st[++tl] = x;
	dfn[x] = low[x] = ++dfntot;
	for(int i = head[x],v; i ;i = e[i].nxt){
		v = e[i].v;
		if(!dfn[v]) Tarjan(v),low[x] = Min(low[x],low[v]);
		else if(ins[v]) low[x] = Min(low[x],dfn[v]);
	}
	if(dfn[x] == low[x]){
		int v; ++qlt;
		do{
			bl[v = st[tl--]] = qlt;
			ins[v] = 0;
		}while(v ^ x);
	}
}
void solve(){
	for(int i = (n<<1);i >= 1;-- i) dfn[i] = head[i] = bl[i] = ins[i] = 0;
	tl = qlt = tot = dfntot = 0;
	for(int i = 1,A,B;i <= m;++ i){
		if((A = Find(E[i][0],E[i][1])) > 1) continue;
		B = Find(E[i][2],E[i][3]);
		if(B > 1) Add_Edge(E[i][0]+n*A,E[i][0]+n*(A^1));//can't be chosen
		else Add_Edge(E[i][0]+n*A,E[i][2]+n*B),Add_Edge(E[i][2]+n*(B^1),E[i][0]+n*(A^1));
	}
	for(int i = 1;i <= (n<<1);++ i) if(!dfn[i]) Tarjan(i);
	for(int i = 1;i <= n;++ i) if(bl[i] == bl[i+n]) return;
	for(int i = 1;i <= n;++ i) putchar('A'+cs[i][bl[i+n] < bl[i]]);
	exit(0);
}
char ff[10][3];
void dfs(int x){
	if(clock() > 1.98*CLOCKS_PER_SEC){
		Put(-1,'\n');
		exit(0);
	}
	if(x == D){
		solve();
		return;
	}
	for(int s = 0;s < 2;++ s) {
		Get(arb[x],ff[x][s]);
		dfs(x+1);
	}
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); D = Read();
	scanf("%s",c+1);
	for(int i = 1;i <= n;++ i)
		if(c[i] == 'x') arb.emplace_back(i);
		else Get(i,c[i]);
	m = Read();
	for(int i = 1;i <= m;++ i) E[i][0] = Read(),E[i][1] = gc()-'A',E[i][2] = Read(),E[i][3] = gc()-'A';
	for(int i = 0;i < D;++ i){
		ff[i][0] = 'a'; ff[i][1] = 'b'; ff[i][2] = 'c';
		shuffle(ff[i],ff[i]+3,ee);
	}
	dfs(0);
	Put(-1,'\n');
	return 0;
}

後記

煉獄版程式碼加個?是因為那個不是煉獄版正解(我懶得寫正解了),而是O2O3+隨機地圖+掐表的程式碼,雖然過了,但不保證每次都能過。

在我寫完題解的時候(2022.3.4 21:42) U群仍未停止討論,有大佬試圖找出 \(O(1.5^dn)\) 的確定性做法,如果討論出來了,我可能會更新。