BZOJ1141: [POI2009]Slw

題目大意:初始有個串S(0)=0,S(i+1)由S(i)經過一次變換得到,變換的規則爲0->1,1->10。現在給出N個整數,令串T=S(a1)+S(a2)+S(a3)+...,其中+代表串聯,問T能否表示爲一個S(X)的子串


md真的神做法..

爲啥POI總是能出出來腦洞這麼大的構造題..

大爺的題解->戳這裏

我們定義逆變換G(S)=S',其中H(S')=S,H函數是題目中定義的

換言之,題目中不斷的把0和1擴展,逆變換就是要把他一點一點的縮回去

1->0,10->1

可以證明這種變換是沒有歧義(唯一對應的),1後面跟1就把前面的1變成0,否則兩個捏在一起變成1

那麼T是S(X)的一個子串就等價於G(T)是G(S(X))也就是S(X-1)的一個子串,由於X可以任意取值,所以換句話說,若T不斷通過逆變換最後可以成爲一個S(X),則T就可以表示成一個子串。如果做到某一步T無法進行逆變換了,那就說明不合法。

那麼如何對T進行逆變換呢?

我們已知T=S(a1)+S(a2)+...+S(an)

若ai均大於0,則G(T)=S(a1-1)+S(a2-1)+...+S(an-1),即分別對每段做逆變換

當有ai爲0時,由於不存在S(-1),沒有辦法直接做逆變換,所以我們進入分類討論

若i=1,也就是說T的開頭是0,由於題目對T前面是什麼沒有要求,那麼我們不妨假設他前面的那個數是1,那麼S(a1)就從0變成了10,也就是說可以把a1當成2

否則若前面的ai-1是偶數,則說明有兩個0相連,那麼無法進行逆變換

若前面的數是5或以上的奇數,則末尾一定爲10101,而101010一定無解,所以我們也可以直接輸出NIE

否則若是1,我們可以把1和0捏在一起形成一個2

若是3,我們可以把3和0捏在一起形成兩個2

捏的過程可以用鏈表來實現(直接暴力也可以,時間複雜度是能保證的)

需要注意的是,當末尾的數是1或者3時,也就是說T的最後一個位置是1,這時由於下一個數可以任意指定,所以最後一位經過變換之後可0可1,就不會對答案造成影響,所以直接刪去這個1就好了


用鏈表寫的好鬧心...還是直接暴力應該會更舒服一點吧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
using namespace std;
int a[N],bef[N],nxt[N];
void del(int x)
{
	bef[nxt[x]]=bef[x];
	nxt[bef[x]]=nxt[x];
}
void doit()
{
	int n;
	scanf("%d",&n);
	int i,j,x,y;
	for(i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		nxt[i-1]=i;
		bef[i+1]=i;
	}
	bef[1]=0;nxt[n]=n+1;
	while(nxt[0]!=bef[n+1])
	{
		i=bef[n+1];
		if(a[nxt[0]]==0) a[nxt[0]]=2;
		if(a[i]==1) del(i),i=bef[i];
		else if(a[i]==3) a[i]=2;
		while(i!=0)
		{
			if(a[i]==0)
			{
				if(a[bef[i]]==1) a[bef[i]]=2,del(i);
				else if(a[bef[i]]==3) a[bef[i]]=2,a[i]=2;
				else {puts("NIE");return;}
			}
			a[i]--;
			i=bef[i];
		}
	}
	puts("TAK");
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	doit();
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章