題目大意:
有一個包含n個點(n=2*k+1)的環(密鑰),環上有k個點爲A,有k個點爲B,剩下一個點爲X。
對於每一個A,從X順時針走到這個A,如果途中A的數目嚴格多於B的數目,那麼這個A爲強的。一個密鑰的特徵值爲強的A的個數。
題目鏈接:http://www.lydsy.com/JudgeOnline/problem.php?id=4900
有三個問題:
1、固定所有A的位置,求特徵值爲0時,X的位置。
2、固定所有A的位置,求特徵值爲S時,X的位置。
3、固定所有B的位置,求特徵值爲S時,X的位置。
題目分析:
先扯一會兒淡、
說實話聽題解的時候我是一臉懵逼的=。=
大佬你在講什麼玄學的東西QAQ
爲毛要轉成括號?爲毛爲毛?
爲毛又搞到樹上了?爲毛爲毛?
考試的時候就想到了O(n)的做法,但是爲毛就是沒調出來?爲毛爲毛?
而且出來的時候有一堆大佬說用後綴數組就可簡單可簡單的能拿到多少多少分啊……現在我也不知道後綴數組要怎麼做。
CTSC滾粗orz。
考察知識點:在循環結構中深入應用數組以及函數的構造與應用。
其實就是亂搞orz。
我們隨便寫一個滿足題意的序列。
XBBAABA
我們知道一個B可以抵消掉一個A的貢獻,那麼我們把X作爲初始位置,給所有的B賦值爲-1,所有的A賦值爲1。那麼一個A爲強的當且僅當這個A所在位置的前綴和大於0。
那上面寫的序列舉例:
X B B A A B A
0-1-2-1 0-1 0 (前綴和)
這個序列中前綴和大於0的A的個數爲0,所以這個序列的特徵值爲0。
由於題中給了X處於每一個位置時的特徵值嚴格取遍0~k,那麼我們其實只要快速算出每一個位置的特徵值,然後找到題目中要求的特徵值的位置即可。
那麼我們一開始可以隨意找一個位置放置X,然後O(n)求出當前序列的特徵值,順道處理一下每個A位置的前綴和。
之後我們可以考慮每次讓X與後面第一個B交換。
假設當前X的位置與後面第一個B之間有x個A。
那我們的這個操作相當於:
1、在序列首端刪去x個A;
2、在序列首端刪去一個B;
3、在序列尾端填上一個B;
4、在序列尾端填上x個A。
我對於當前序列的特徵值記爲num。
對於1、2兩個操作,就相當於是每次刪掉一個數,然後對整個序列進行+1或者-1。然而我們不可能把每個數真的+1或者-1,那麼我們可以調整“0”的值。我們原來要求的是前綴和大於0的A有多少個,把這個序列整體+1,相當於把“0”的值-1,就是說這個問題轉化成了【原】序列有多少個前綴和大於-1的數。
num原本記錄的是大於0的A有多少個,現在詢問大於-1的A有多少個,只需要在原num的基礎上加上前綴和爲-1的A的個數。
這樣我們再開一個數組sum,sum[i]記錄前綴和爲i的A的個數。
sum數組只需要在添加一個A和刪除一個A的時候操作。
1、2操作就可以這樣處理。
3操作不影響答案所以不用管它。
對於4操作,在末尾增添x個A,我們要把在序列尾填上一些數。
我們不能重新計算前綴和,但是我們可以發現,序列的最後一項前綴和一定爲0(因爲前面所有的A和B都抵消了),這樣我們從序列尾倒着加回去,就知道每一項的值了,改一下sum數組即可。
這樣可以在把這個序列掃一遍的時間內算出所有的每個位置的num值,當得到答案所求的num值時即可輸出答案。
時間複雜度是O(n)。
第一問和第二問都可以這麼搞,第三問的話仿照第一問和第二問的過程自己推一下,都是差不多的。
代碼如下:
#include <cstdio>
#include <cstring>
#define N 20000005
using namespace std;
int p[N];
int sum[N],a[N];
bool b[N];
int seed,n,k,S;
int getrand(){
seed=((seed*12321)^9999)%32768;
return seed;
}
void generateData()
{
scanf("%d%d%d",&k,&seed,&S);
int t=0;
n=k*2+1;
for(int i=1;i<=n;i++)
{
p[i]=(getrand()/128)&1;
t+=p[i];
}
int i=1;
while(t>k)
{
while(p[i]==0) i++;
p[i]=0;
t--;
}
while(t<k)
{
while(p[i]==1) i++;
p[i]=1;
t++;
}
}
void A(int S)
{
memset(sum,0,sizeof(sum));
int zero=k,last=zero,now=1,st,num=0,tmp=0;
while(p[now]) now++;
st=now; now++; if(now>n) now=1;
while(now!=st)
{
if(p[now])
{
a[now]=++last;
sum[a[now]]++;
if(a[now]>zero) num++;
}
else a[now]=--last;
now++;
if(now>n) now=1;
}
while(num!=S || (num==S && p[now]))
{
now++;
if(now>n) now=1;
if(p[now])
{
zero++;
num-=sum[zero];
sum[zero]--;
tmp++;
}
else
{
num+=sum[zero];
zero--;
for(int i=0;i<tmp;i++)
sum[zero-i]++;
tmp=0;
}
}
printf("%d\n",now);
}
void B(int S)
{
memset(sum,0,sizeof(sum));
int zero=k,last=zero,now=1,st,num=0,tmp=0;
while(p[now]) now++;
st=now; now++; if(now>n) now=1;
while(now!=st)
{
if(p[now]) --last;
else
{
a[now]=++last;
sum[a[now]]++;
if(a[now]>zero) num++;
}
now++;
if(now>n) now=1;
}
while(num!=S || (num==S && p[now]))
{
now++;
if(now>n) now=1;
if(p[now])
{
num+=sum[zero];
zero--;
tmp++;
}
else
{
zero++;
num-=sum[zero];
sum[zero]--;
sum[zero+tmp]++;
if(tmp>0) num++;
tmp=0;
}
}
printf("%d\n",now);
}
int main()
{
freopen("cipher.in","r",stdin);
freopen("cipher.out","w",stdout);
generateData();
A(0);
A(S);
B(S);
fclose(stdin);
fclose(stdout);
return 0;
}