CSP-S遊記
初賽
初賽的事誰還記得啊
去考了場出賽,MAD,什麼完善程式啊,直接爆炸了好不好,那兩道四分題都不是很會做,其他都還行,拿到82分左右滿意退場。
複賽
T1
看了一眼,眼都白了,什麼亂七八糟的東西啊。
然後就在死命的亂膜,腦子都是糊的,一個半小時後過了大樣例,以為勝券在握了。
T2
當時看了一眼,這不是離散化SB題,然後瘋狂的打完了程式碼,一看到\(p\)(忘了是\(p\)還是\(q\))互不相等,又傻了,這不是直接爆搞?然後又改了會程式碼,半個小時才搞完。
T3,T4
最後兩個小時看了眼題,\(T4\)看了一會明白了博弈策略後想到\(nlogn\)做法(實際上我當時根本沒想全,還想複雜了,不可能實現的出來,後來膜了會題解才實現出來。),T3總覺得很簡單,然來在紙上亂搞,三十多分鐘後發現了新大陸,然後搞出來了,對著樣例做了一遍,真的過了大樣例!!!
然後就開始了瘋狂碼的生活,最後剩半個小時,最後一道題目連暴力都碼不出來了,然後自暴自棄。
估分是\(250\)(盲猜第一題爆炸)。
賽後
成績還沒出,跑去你谷交了發,第一題爆炸了啊啊啊啊,還只有\(20\)分,然後只有\(220\)了,可惡的第一題!!!!!!
後來看了眼第四題的題解,思路比我清晰很多,直接處理出每一步吃掉了誰,會更方便博弈處理哪一步能走或者不能走(我一開始是想遞迴處理,但是這樣處理非常的繁瑣,我估計是實現不出來的)
然後再運用人類智慧想了想,最終想出了第四道題。很多提示就對了
第四題升黑了,或許就是我現在唯一的心裡安慰吧。
回去搞課內了
題解
儒略曆
暴力亂搞,沒什麼好說了的。
程式碼肯定不好懂,畢竟不同的人有不同的想法。
#include<cstdio> #include<cstring> using namespace std; typedef long long LL; //預設公元前-1 LL moday[2][16]={{0,31,28,31,30,31,30,31,31,30,31,30,31,0,0,0},{0,31,29,31,30,31,30,31,31,30,31,30,31,0,0,0}}; //LL yeday1[4]={365,366,0,0};//儒略曆 //LL yeday2[3]={365,1461,146097};//格里高利曆 struct node { LL yea,mo,da; }jb1,jb2; LL limit=2299161; //LL daylimit=277; //基本 inline int ruluepd(node x){return x.yea%4==0;} inline void rulue(node &x,LL &k) { while(x.yea%4!=0 && k>=365)k-=365,x.yea++; if(k>=366)//進入閏年 { k-=366;x.yea++; LL t=k/1461; k-=t*1461; x.yea+=t*4; while(k>=365+ruluepd(x))k-=365+ruluepd(x),x.yea++; } int t=ruluepd(x); for(int i=1;i<=12;i++) { if(k>=moday[t][i])k-=moday[t][i],x.mo++; else break; } x.da+=k; } inline int gepd(node x){return x.yea%400==0 || (x.yea%4==0 && x.yea%100!=0);} inline void gelai(node &x,LL &k) { if(k<=16) { x.da+=k;return ; } k-=17;x.mo++;x.da=1; if(k<61) { if(k>=30)x.mo++,x.da=1+(k-30); else x.da=1+k; return ; } k-=61; x.yea++;x.mo=1;x.da=1; LL t=k/146097; k-=t*146097;x.yea+=t*400; while(!gepd(x) && k>=365) { k-=365,x.yea++; } if(k>=366) { //x在閏年地帶 while(x.yea%100!=0 && k>=1461)k-=1461,x.yea+=4; //100年為36525 while(k>=36524+gepd(x))k-=36524+gepd(x),x.yea+=100;//一百年沒了 while(k>=1461-(!gepd(x)))k-=1461-(!gepd(x)),x.yea+=4; //最後只剩下三年 while(k>=365+gepd(x))k-=365+gepd(x),x.yea++; } int tt=gepd(x); for(int i=1;i<=12;i++) { if(k>=moday[tt][i])k-=moday[tt][i],x.mo++; else break; } x.da+=k; } void solve(LL k/*過的時間*/) { node x; if(k>=limit) { k-=limit; x=jb2; gelai(x,k); printf("%lld %lld %lld\n",x.da,x.mo,x.yea); } else { x=jb1; rulue(x,k); if(x.yea<=0) { x.yea--; printf("%lld %lld %lld BC\n",x.da,x.mo,-x.yea); } else printf("%lld %lld %lld\n",x.da,x.mo,x.yea); } } int main() { // freopen("julian.in","r",stdin); // freopen("julian.out","w",stdout); jb1.yea=-4712;jb1.mo=1;jb1.da=1; jb2.yea=1582;jb2.mo=10;jb2.da=15; int T;scanf("%d",&T); while(T--) { LL x;scanf("%lld",&x); solve(x); } return 0; }
動物園
如果你發現一個位置沒有任何位置或者出現在動物中,則一定可以,否則一定不行,因為\(q\)互不相同。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100
#define M 1100000
#define MM 2100000
using namespace std;
typedef unsigned long long LL;
template<class T>
inline void getz(T &x)
{
x=0;char c=getchar();
while(c>'9' || c<'0')c=getchar();
while(c<='9' && c>='0')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
bool vis[N];//這個進位制是否被用了
LL b[M];//表示畜生的編號
int n,m,kk,c;
LL ans=0;
int limit;//表示限制位數
LL fc[N];
int main()
{
getz(n);getz(m);getz(c);getz(kk);
fc[0]=1;for(int i=1;i<kk;i++)fc[i]=fc[i-1]*2;
for(int i=1;i<=n;i++)getz(b[i]);
for(int i=0;i<kk;i++)vis[i]=1;
for(int i=1;i<=m;i++)
{
int x,y;getz(x);getz(y);
vis[x]=0;
}
for(int j=0;j<kk;j++)
{
for(int i=1;i<=n;i++)
{
if(vis[j])break;
if((b[i]&fc[j]))vis[j]=1;
}
}
for(int i=0;i<kk;i++)limit+=vis[i];
if(kk==64 && limit==64)
{
if(!n)printf("18446744073709551616\n");//對於2^64要獨立判斷
else
{
n--;
LL x=0;
for(int i=0;i<kk;i++)x+=fc[i];
printf("%llu\n",x-(LL)n);
}
}
else printf("%llu\n",((LL)1<<limit)-(LL)n);
return 0;
}
函式呼叫
一道藍題想了我40min。。。
沒有遞迴,非常明顯,DAG圖。
首先,可以把詢問也看成一個點,那麼就是求這個點的影響。
如果用線段樹,非常簡單的思路就是把所有的加法全部移動到乘法的後面,由於加法不會互相影響,所以可以認為,如果一個加法後面沒有乘法,其就是獨立的,如果所有的操作都是獨立的,那麼他們怎麼調換順序都可以。
也就是把所有的加法乘上後面的乘法操作。
好,那麼關鍵來了,我們需要維護每個操作都是獨立的情況下其的貢獻需要乘以幾倍?
例如,已知函式:\(g(1)=1\) \(1\) \(2\),\(g(2)=2\) \(3\),\(g(3)=\) \(3\) \(2\) \(1\) \(2\)那麼我們就只需要讓\(g(1)\)執行的過程中加法都必須乘\(3\)即可。
再例如:\(g(1)=1\) \(1\) \(2\),\(g(2)=3\) \(2\) \(1\) \(1\),\(g(3)=2\) \(3\),\(g(4)=\) \(3\) \(3\) \(2\) \(3\) \(2\),這個例子就比較複雜了,但是我們仔細看一下\(g(4)\),你會發現,讓\(g(2)\)的所有加法乘\(3\)和執行一次\(g(2)\)不就相當於\(g(2)*4\)嗎?也就是說在獨立之後,每個函式需要乘的倍數是可以用加法合併的,所以只需要從根節點往下跑拓撲排序即可。
時間複雜度:\(O(n+m)\)
#include<cstdio>
#include<cstring>
#define N 110000
#define M 1100000
#define MN 1200000
using namespace std;
typedef long long LL;
const LL mod=998244353;
template<class T>
inline void getz(T &x)
{
x=0;char c=getchar();
while(c>'9' || c<'0')c=getchar();
while(c<='9' && c>='0')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
struct node
{
int y,next;
}a[MN];int len,last[N];
bool v[N];//迴圈使用
inline void ins(int x,int y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
int list[MN],head,tail;
int in[N];
int n,m,Q;
inline void bfs()
{
memset(v,0,sizeof(v));
list[head=tail=1]=m;v[m]=1;
while(head<=tail)
{
int x=list[head++];
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
in[y]++;
if(!v[y])
{
v[y]=1;
list[++tail]=y;
}
}
}
}
int qu[N];
LL aa[N];//表示每個數字
struct CHANGE
{
int x;
LL y,z;//如果是3的話,y後面存的是乘積,前面存長度,z存的是翻倍次數
}ch[N];
int xl[MN],ll[N],rr[N],top;
LL sum[MN];
void pre_do()//處理拓撲序
{
head=1;tail=1;list[1]=m;
while(head<=tail)
{
int x=list[head++];
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
in[y]--;
if(!in[y])list[++tail]=y;
}
}
for(int i=tail;i>=1;i--)
{
int x=list[i];
if(ch[x].x==3)
{
LL now=1;
for(int j=rr[x];j>=ll[x];j--)
{
int y=xl[j];
sum[j]=now;
if(ch[y].x==3 || ch[y].x==2)now=now*ch[y].y%mod;
}
ch[x].y=now;
}
}
}
void solve()
{
for(int i=1;i<=n;i++)aa[i]=(aa[i]*ch[m].y)%mod;
ch[m].z=1;
for(int i=1;i<=tail;i++)
{
int x=list[i];
if(ch[x].x==3)
{
for(int j=ll[x];j<=rr[x];j++)
{
int y=xl[j];
if(ch[y].x==1)aa[ch[y].y]=(aa[ch[y].y]+(ch[y].z*ch[x].z%mod*sum[j]%mod))%mod;
else if(ch[y].x==3)ch[y].z=(ch[y].z+(ch[x].z*sum[j]%mod))%mod;
}
}
}
}
int main()
{
getz(n);
for(int i=1;i<=n;i++)getz(aa[i]);
getz(m);
for(int i=1;i<=m;i++)
{
getz(ch[i].x);
if(ch[i].x==1)getz(ch[i].y),getz(ch[i].z);
else if(ch[i].x==2)getz(ch[i].y);
else
{
getz(ch[i].y);
ll[i]=top+1;
for(int j=1;j<=ch[i].y;j++)
{
getz(xl[++top]);
if(!v[xl[top]])
{
v[xl[top]]=1;
ins(i,xl[top]);
}
}
rr[i]=top;
for(int j=ll[i];j<=rr[i];j++)v[xl[j]]=0;
}
}
getz(Q);m++;
ll[m]=top+1;
ch[m].x=3;
for(int i=1;i<=Q;i++)
{
getz(xl[++top]);
if(!v[xl[top]])
{
v[xl[top]]=1;
ins(m,xl[top]);
}
}
rr[m]=top;
bfs();
pre_do();
solve();
for(int i=1;i<=n;i++)printf("%lld ",aa[i]);
printf("\n");
return 0;
}
貪吃蛇
妙啊。
先講講博弈策略,如果對於\(x\)吃掉了\(y\),後面\(x\)沒有再吃蛇的機會且會被吃掉,那麼\(x\)就不敢吃\(y\),那麼這個該怎麼運用呢?
不難發現的事情是每次吃掉的順序都是相同的,因此我們可以考慮把吃人的順序求出來,用\(list\)儲存起來,然後\(eat\)儲存這個數字什麼時候被吃掉。
那麼對於上面哪一條,我們該怎麼求出最小的蛇數呢?其實最小的蛇數就是經過最大的輪數。
我們再\(list\)從後往前搜,用\(now\)表示吃蛇會在第\(now\)輪停止,所以一開始\(now=n\)。
這樣,當我們搜到了\(list[i]\),如果\(eat[list[i]]>=now\),證明其不會被吃掉,那麼他敢吃,否則他不敢吃(因為如果他吃了的話,中間的蛇都是敢吃的,他就會被吃掉),現在就要停下來,然後\(now=i\)。(這一切的前提都是建立在蛇足夠聰明,有大局觀的情況下)
好,現在我們需要考慮的是如何構建出這個\(list\)陣列。
Part1
暫時先不考慮編號問題,只考慮數值,因此下述會有許多不嚴謹的地方。
考慮對於陣列\(a\),對於\(a_{n}\),不斷的吃,吃到\(a_{k}\)時小於\(a_{n-1}\)了,此時得到了\(a_{n}'\),那麼如果此時的\(a_{n}'≥a_{k}\)的話,對於\(a_{n-1}\),\(a_{n-1}≤a_{n},a_{k+1}≥a_{k}\),所以\(a_{n-1}-a_{k+1}≤a_{n}'\),因此我們只需要把減完之後的\(a_{n-1}\)放在\(a_{n}\)前面就行了,也就是有單調性。
好,什麼時候取等號呢?只當\(a_{n-1}=a_{n},a_{k+1}=a_{k}\),開始考慮編號,這是不難發現的事情,\(a_{n-1}\)的編號一定小於\(a_{n}\),所以放前面也是沒有問題的,不破壞單調性。
但是,如果\(a_{n}'\)小於\(a_{k+1}\),額,也就是減完之後就變成了一個最小的數字,單調性被破壞,這個時候就要考慮別的性質了。
Part2
不難發現的是:\(\frac{a_{n}}{2}<a_{k}\),那麼我們就猜了,後面的數字都是這麼大的數字,那麼有沒有可能一個數字只要吃了最小的數字,他就會變成最小的數字呢?(忽略編號)
事實上,確實如此,這個手艹一下,會比較容易理解,設\(t\)陣列表示每次減得到的值,只需要證明\(t_{i}≤a_{k}\)即可。
首先對於\(t_{1}=a_{n}-a_{k}\),滿足要求,對於\(t_{2}\),如果\(a_{n-1}=a_{n}\),那麼\(t_{2}=a_{k}\),如果\(a_{n-1}≠a_{n}\),那麼設\(u=a_{n}-a_{n-1}\),那麼會得到\(a_{k}-u\),不妨設\(a_{k}'=a_{k}-u\),那麼\(t_{2}=a_{k}'\)。
對於\(a_{n-1}=a_{n}\),如果\(a_{n-2}=a_{n-1}\),那麼\(t_{3}=t_{1}\),重複如此,如果\(a_{n-2}≠a_{n-1}\),那麼\(t_{3}=a_{n-2}-a_{k}<t_{1}\),同樣也是重複如此,不過要把\(a_{n-2}\)看成\(a_{n}\)。
對於\(a_{n-1}<a{n}\),那麼得到的\(a_{k}-u\),不妨認為\(a_{k}'\)就是後面的\(a_{k}\),同樣重複如此。
不難發現,過程中\(t_{i}<=a_{k}\),且對於奇數\(i\),\(t_{i}≤t_{1}\),且當僅當\(a_{n}=a_{n-1}\)時,\(t_{3}=t_1\),其餘同理。
得證。
但是,如果考慮編號的話,在等於\(a_{k}\)的時候,如果\(a_{k}=a_{k+1}\),就會造成編號的錯亂。
Part3
但是我們發現,雖然編號會亂,但是每次操作的數字是不受影響的,因為我們只需要考慮數值都是\(a_{k}\)的編號。
不妨考慮離線處理一下,對於\(t(t>=0)\),如果\(a_{n-2t}=a_{n-2t-1}\)(前面都滿足這個要求的情況下),那麼其實就是\(a_{n-2t}\)吃掉了一個\(a_{t}\),然後\(a_{n-2t-1}\)又將其吃掉並填上去。
不妨把一開始值為\(a_{k}\)的編號列出來,那麼實際上我們就是要支援兩個操作:刪除最小的編號並返回,在刪除之後插入一個編號。
一般遇到這種情況,只需要直接用連結串列。
但是連結串列怎麼搞還是個問題,不妨把時間建一個連結串列,然後利用桶排序把編號排起來(因為是編號,所以只需要用桶排序就可以了),從下往上搜,對於每個點,只需要看連結串列中其插入的時間的點的右端點就是這個點被刪掉的時間,同時刪掉右端點。
但是有個問題,如果這個點插入的時間已經被刪掉了,怎麼處理?
但是我們發現,一個點找完右端點他就無事可幹了,同時其原本右端點的右端點就是現在他的右端點。
所以只需要把刪除的時間所代表的插入的點放到左端點即可。
這樣,就完成了連結串列處理編號。
Part4
當然,如果剩下的數字都是不同的,那麼就嚴格小於\(a_{k}\)了,這時我們只需要從右往左跑即可。
但是還有一種情況,如果在\(Part3\)的時候,所有的位置都滿足要求,直到\(a_{n-2t}=a_{k}\)的話,這樣可能連結串列就比較難處理了,這個時候因為一段的值都是\(a_{k}\),隨便處理一下即可。
程式碼
時間複雜度:\(O(Tn)\)
#include<cstdio>
#include<cstring>
#define N 1100000
using namespace std;
inline int mymax(int x,int y){return x>y?x:y;}
inline int mymin(int x,int y){return x<y?x:y;}
int n,nn;
int turn[N],list[N]/*被吃掉的順序序列*/,now;//其最後一次吃別人之前剩下了多少數字,被?吃了。
inline void set_eat(int x,int y,int type=0)//x吃掉了y,type等於0表示不計入list
{
if(type==0)list[++now]=x,turn[y]=now;//被吃掉的可憐孩子
else list[type]=x,turn[y]=type;
}
struct node
{
int x,id;
node(int xx=0,int yy=0){x=xx;id=yy;}
}a[N],b[N],sta[N];int he1,ta1,he2,ta2;
inline bool operator<(node x,node y){return x.x==y.x?x.id<y.id:x.x<y.x;}
inline bool operator>(node x,node y){return x.x==y.x?x.id>y.id:x.x>y.x;}
inline node operator-(node x,node y){return node(x.x-y.x,x.id);}
inline node find_small_top()
{
if(he1>ta1 || (he2<=ta2 && sta[he2]<a[he1]))return sta[he2];
else return a[he1];
}
inline node find_big_top()
{
if(he1>ta1 || (he2<=ta2 && sta[ta2]>a[ta1]))return sta[ta2];
else return a[ta1];
}
inline void pop_small_top()
{
if(he1>ta1 || (he2<=ta2 && sta[he2]<a[he1]))he2++;
else he1++;
}
inline void pop_big_top()//返回是彈出了sta還是彈出了a
{
if(he1>ta1 || (he2<=ta2 && sta[ta2]>a[ta1]))ta2--;
else ta1--;
}
inline void add_front(node x){sta[--he2]=x;}
void solve1()//第一部分,類似之前的一道題目,需要注意的是,這樣子跑前面剩下來的數字絕對不可能是0,因為退出的先決條件是>1/2
{
while(now<nn)
{
node x=find_big_top();
pop_big_top();
while(now<nn && x>find_big_top())
{
node y=find_small_top();
if(y.x>x.x/2)
{
sta[++ta2]=x;
return ;
}
pop_small_top();
set_eat(x.id,y.id);
x=x-y;
}
if(nn==now)return ;
else add_front(x);
}
}
int tong[N];//桶,裡面裝的是對應的連結串列編號。
struct LIA
{
int l,r,id/*承載的id*/,ti;
}lian[N];
inline void del(int x){lian[lian[x].l].r=lian[x].r;lian[lian[x].r].l=lian[x].l;}//非常簡單的刪除
void solve2()
{
int top=0;
while(he1<=ta1 && he2<=ta2)
{
if(a[he1]<sta[he2])a[++top]=a[he1++];
else a[++top]=sta[he2++];
}
while(he1<=ta1)a[++top]=a[he1++];
while(he2<=ta2)a[++top]=sta[he2++];
//類似歸併排序,將其合併到一起去
//現在就是1~top了
//首先如果a[top]吃掉了a[1]然後a[top-1]=a[top],那麼a[top-1]佔掉a[top-1]的位置
ta2=0;//迴圈利用
memset(tong,-1,sizeof(tong));
while(now<nn-1/*至少有三個數字沒有被吃*/ && a[top].x!=a[1].x)
{
if(a[top].x==a[top-1].x)
{
sta[++ta2]=node(a[top-1].x,a[top].id/*因為後面就是用top的id去吃別人的*/);//新增加了一個成員
lian[ta2].ti=++now;tong[a[top-1].id]=ta2;lian[ta2].id=a[top-1].id;//這裡就要放桶了,相當於把後面不能放的在前面放好了
set_eat(a[top-1].id,a[top].id);
top-=2;
}
else break;
}
//連結串列時間到
for(int i=1;i<=top && a[i].x==a[1].x;i++)tong[a[i].id]=0;
lian[0].l=ta2+1;lian[0].r=1;for(int i=1;i<=ta2;i++)lian[i].l=i-1,lian[i].r=i+1;//單純的去處理連結串列
lian[ta2+1].l=ta2;
//為接下來的連結串列準備就緒
int l=1;ta1=0;//迴圈利用,表示存活下來的那些編號
while(l<=n)
{
while(tong[l]==-1 && l<=n)l++;
if(l>n)break;
int x=tong[l];//取出你連結串列的編號。
int y=lian[x].r;
if(y==ta2+1)a[++ta1]=node(a[1].x,l);//不會被刪除
else
{
set_eat(sta[y].id,l,lian[y].ti);//吃掉,l消失
lian[x].id=lian[y].id;tong[lian[x].id]=x;//載體的掉包
del(y);//把y給刪掉
}
l++;
}
//連結串列結束,此時a陣列被重新整合
if(a[1].x==a[top].x)//全部都是同一個數字,此時需要兩個數字兩個數字向前推進
{
while(now<nn)
{
set_eat(a[top].id,a[top-1].id);
a[top-1]=a[top];top--;
if(top>1)set_eat(a[top-1].id,a[top].id),top--;//你變成了0肯定要給別人吃掉啊
}
}
else//只需要記住一個變數,此變數為唯一變數。
{
node x=a[1];
while(now<nn)
{
set_eat(a[top].id,x.id);
x=a[top];
top--;
}
}
}
int main()
{
int T;scanf("%d",&T);
for(int t=1;t<=T;t++)
{
now=0;
memset(turn,0,sizeof(turn));
if(t==1)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i].x);
b[i].id=i;
}
}
else
{
int k;scanf("%d",&k);
for(int i=1;i<=k;i++)
{
int x,y;scanf("%d%d",&x,&y);
b[x].x=y;
}
}
memcpy(a,b,sizeof(b));
nn=n-1;
he1=1;ta1=n;he2=n+1;ta2=n;
solve1();
if(now<nn)solve2();
now=nn+1;//表示進行到哪個位置停下來
for(int i=nn;i>=1;i--)
{
if(turn[list[i]] && turn[list[i]]<now)now=i;//就在這個時間停下來
}
printf("%d\n",n-now+1);
}
return 0;
}