P3777卡牌操作
時間限制 : - MS 空間限制 : 165536 KB
評測說明 : 1000ms
問題描述
有n張卡片在桌上一字排開,每張卡片上有兩個數,第i張卡片上,正面的數爲a[i],反面的數爲b[i]。現在,有m個熊孩子來破壞你的卡片了!
第i個熊孩子會交換c[i]和d[i]兩個位置上的卡片。
每個熊孩子搗亂後,你都需要判斷,通過任意翻轉卡片(把正面變爲反面或把反面變成正面,但不能改變卡片的位置),能否讓卡片正面上的數從左到右單調不降。
輸入格式
第一行一個n。
接下來n行,每行兩個數a[i],b[i]。
接下來一行一個m。
接下來m行,每行兩個數c[i],d[i]。
輸出格式
m行,每行對應一個答案。如果能成功,輸出TAK,否則輸出NIE。
樣例輸入
4
2 5
3 4
6 3
2 7
2
3 4
1 3
樣例輸出
NIE
TAK
提示
【樣例解釋】
交換3和4後,卡片序列爲(2,5) (3,4) (2,7) (6,3),不能成功。
交換1和3後,卡片序列爲(2,7) (3,4) (2,5) (6,3),翻轉第3張卡片,卡片的正面爲2,3,5,6,可以成功。
n≤200000,m≤1000000,0≤a[i],b[i]≤10000000,1≤c[i],d[i]≤n.
來源 POI 2014
最好奇的是這種牌有什麼用…
題解
像這麼大的數據,還要多次詢問,那麼每一次的操作時間複雜度絕對不超過log n
那順着這個思路走下去,對於一段區間,進行時間複雜度爲log n的操作
那只有可能是——二分
那麼怎麼進行二分呢?
對於一段牌[l,r]是否能成爲一段單調不遞增區間 首先能夠得到它要滿足的最簡單的條件就是
[l,mid] 和 [mid+1,r]一定首先可以成爲單調不遞增區間
再其次纔是 這兩段區間能否合併成爲一個單調不遞增區間
所以說 我們就想到了一個可能的操作——不停二分,直到區間長度變成1
當區間長度變爲1時,那麼它自身自然就是一個單調不遞增序列
那麼如何對兩段區間合併之後的大區間能否成爲一個單調不遞增區間進行討論呢?
前面都是瞎扯 解題現在開始
那麼我們首先就要確定一些狀態了
首先對於一張牌只有一種值的情況進行討論
對於兩段區間[l,mid],[mid+1,r]能否合併成一個區間,我們肯定需要以下幾個參數
f[l,r] ⇒ [l,r]能否形成單調不遞減區間
num[x] ⇒ x點的值
那麼簡單分析就能得到合併條件
①f[l,mid]==True
②f[mid+1,r]==True
③num[mid]<=num[mid+1]
如此一來就可以確定兩段區間是否合併成一個單調不遞減序列(必須全部滿足)
推廣到一個點有兩個值的情況
由於一段區間能否成爲一個單調不遞減序列與這段區間的開頭值與結尾值相關
而每一段區間的開頭和結尾都有了兩種可能(不算不成立的情況)
那麼我們可能就需要多一些參數了
Z[l,r] ⇒ 第一張牌爲正面時的最後一張牌的值
F[l,r] ⇒ 第一張牌爲反面時的最後一張牌的值
numz[x] ⇒ x點的正面值
numf[x] ⇒ x點的負面值
其中 當[l,r]不能以第一張牌爲正面作爲開頭時 Z[l,r]=-1
F[l,r]=-1的情況同理
可以先停下來思考一下爲什麼要存Z和F
於是對於這種兩面牌的單調不遞減序列 就有了判定方法
首先判定第一張牌爲正面的情況
①Z[l,mid]!=-1
②Z[mid+1,r]!=-1
③Z[l,mid]<=numz[mid+1]
④F[mid+1,r]!=-1
⑤Z[l,mid]<=numf[mid+1]
其中 ①②③ 成立 或 ①④⑤ 成立即可更新
(①②③成立 Z[l,r]=Z[mid+1,r]
①④⑤成立 Z[l,r]=F[mid+1,r])
若兩者同時成立 則取更小值
而這種更新的操作方法就是線段樹(點修改)
提示
強行使numz[x]<=numf[x]可以簡化部分操作
順便偷個懶 有一些細節我就不細說了
附上對拍代碼
#include <iostream>
#include <cstdio>
#define ls (ori<<1)
#define rs (ori<<1|1)
#define mid (l+r>>1)
using namespace std;
int z[201234],f[201234],x;
int tailz[1601234],tailf[1601234];
inline int input()
{
char c=getchar();int o;
while(c>57||c<48)c=getchar();
for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
return o;
}
void flash(int l,int r,int ori)//更新操作
{
if(tailz[ls]==-1)tailz[ori]=-1;
else
{
if(tailz[rs]!=-1&&tailz[ls]<=z[mid+1])tailz[ori]=tailz[rs];
else if(tailf[rs]!=-1&&tailz[ls]<=f[mid+1])tailz[ori]=tailf[rs];
else tailz[ori]=-1;
}
if(tailf[ls]==-1)tailf[ori]=-1;
else
{
if(tailz[rs]!=-1&&tailf[ls]<=z[mid+1])tailf[ori]=tailz[rs];
else if(tailf[rs]!=-1&&tailf[ls]<=f[mid+1])tailf[ori]=tailf[rs];
else tailf[ori]=-1;
}
}
void BT(int l,int r,int ori)
{
if(l==r)tailz[ori]=z[l],tailf[ori]=f[l];//當區間長度爲1時的操作
else
{
BT(l,mid,ls),BT(mid+1,r,rs);
flash(l,r,ori);
}
}
void UD(int l,int r,int ori)
{
if(l==r)tailz[ori]=z[l],tailf[ori]=f[l];
else
{
//細分區間直到找到修改的那個點(區間長度爲1)爲止
if(x<=mid)UD(l,mid,ls);
else UD(mid+1,r,rs);
flash(l,r,ori);
}
}
int main()
{
// freopen("In.txt","r",stdin);
int n=input(),m,a,b;
for(int i=1;i<=n;i++)
{
a=input();b=input();
if(a>b)z[i]=b,f[i]=a;
else z[i]=a,f[i]=b;
}
BT(1,n,1);
m=input();
for(int i=1;i<=m;i++)
{
a=input();b=input();
swap(z[a],z[b]);
swap(f[a],f[b]);
x=a;UD(1,n,1);
x=b;UD(1,n,1);
if(tailz[1]==-1&&tailf[1]==-1)puts("NIE");
else puts("TAK");
}
}