涼心模擬Day1題解
道歉
由於T2出題人@fengsongquan 不知道他提供題目有原題。
@fengsongquan 道歉原帖
抱歉,第二題題是我看一本數學書(《趣味學數學》(圖靈出版社))的時候想到的,沒想到POJ已經有原題了,我出這題之前根本就沒看到過這個原題,也沒有仔細查有沒有重題,於是就這樣了,我向大家再次道歉,不要再懟了QwQ
且由於我們驗題組沒有在poj上做過這題,導致我們並沒有發現這道題是原題。給洛谷管理員和廣大OIER造成了不好的體驗與影響。
在這裡向全體OIER們和洛谷管理道歉!!!
希望各位OIER能夠原諒我們,以後我們一定會改進,爭取能做的更好。
在這裡再次向各位道歉!
前言
本來早就準備好了題解,但由於比賽出了鍋,我們斟酌再三,還是把題解放了上來。
本場模擬賽為本團隊原創(T2不是)的題目,難度NOIP提高組,三道題都有驗題人驗過題了,題目應該不會有太大問題。
本題解按分數檔給出題解。
第一檔
第一題做法很多,因為資料純隨機,所以可以直接暴搜記憶化,或者一些奇怪的貪心都能過。但是注意記憶化的話記憶體不能開一億個int,可以用記憶體更小的型別來代替。拿不到100分也有70分。
第二題沒什麼想法的話直接特判掉1,2兩個點,20分還是穩的。寫個40分也可以。
第三題看不懂題意可以直接棄療….
70+20=90,這個基本分還是要有的。
第二檔
第一題100分。
第二題直接暴搜記下所有可能的狀態,可以拿到40分。
第三題個人認為拿24分的唯一難點就是看懂題意(Gold_7在花了20分鐘後認為題面描述沒有問題),直接按照題目描述模擬即可(雖然有點麻煩)。
100+40+24=164,這個應該大多數OIer都能拿到。
第三檔
第一題AC。
第二題40分,第三檔出題人沒有給出合理的解決方案,或許能拿到60分。
第三題可以加個字首和優化能拿到第二個點,48分到手,第三個點可以暴力第一個操作之後使用線段樹維護,可以再拿24分。
那麼可以有100+40+72=212分,這個應該算一個比較高的分數了。
第四檔
接下來關鍵是要二三兩題中挑一道拿到更高分數,對於一般提高組水平的選手可能只能花較多時間在其中一道上。可以看出相對來說第二題更考察思維能力,第三題更考察程式碼能力。這裡推薦A掉第二題。
第二題可以看出是一道結論題。Retcarizy第一眼看到這道題就感覺是與某種東西的奇偶性有關。我們考慮如何使得空格移動之後奇偶性不變。一種方法是將圖中格子按蛇形的方式展開,就像這樣:

我們去掉空格後得到了一個這樣的序列:
14,2,6,7,3,13,1,5,4,11,12,15,9,10,8。
可以發現,無論怎麼移動格子,該序列的逆序對個數的奇偶性不會變。並且該序列的逆序對個數不為0或1時可以通過操作使得逆序對個數減少2。所以對於初始狀態和最終狀態逆序對個數奇偶性不同時一定不可行,而且都是偶數的情況下由於逆序對個數為0的排列是唯一的,所以一定可行。但是對於都是奇數的情況下乍看難以得到一個可行的證明方案。這個問題可以先猜個結論,具體的證明已經有數學家給出了,但是證明過程有點長,這裡就不提供了。
所以我們就求個逆序對個數判下奇偶性相不相同就可以了。其中第7個點是暴力求逆序對(雖然覺得這檔部分分沒什麼用)。
100+100+72=272分,已經是一個很高的分數了。
第五檔
最後一檔當然就是AK了。
大佬在A掉前兩題之後可以開始搞第三題了。第三題是我花了很長時間出出來的水題….其實思維難度並不高,個人認為學過線段樹和矩陣乘法的都能想到正解(只不過程式碼細節有點多而且難調)讀懂題目之後可以大概總結為以下幾個:
區間第i項做某種操作i次,
區間做某項操作,
詢問區間和。
這樣我們可以想到矩陣。通過矩陣方面的理論我們可以這樣描述:
設一次傻逼風暴的轉移矩陣為A,事件1相當於對一段區間分別加上$A^1,A^2,A^3,A^4……$
事件2和事件3都相當於對一段區間乘上一個矩陣。
事件4和事件5都可以通過對一段區間的矩陣求和來實現。
我們知道矩陣符合分配率,矩陣乘法符合結合律(學過矩陣乘法的人往這個方向想一下也不是很難,其實特別好證),所以可以直接扔到線段樹上來維護,標記和維護的鍵值都是用矩陣來描述的。
由於常數比較大,所以把時間限制開到了10s(實測O(鬆)還是隻能拿48分)(標程在常數極其不優秀的情況下極限資料跑了7s)。還有一點要注意,本人在題面中均使用集合的方式來描述區間,所以可以l>r,為了保證寫出正解的人能拿到高分所以我放在了第一個點(對暴力來說並沒有什麼關係)。
標程
T1 天下第一(peerless)
(法一) 記憶化搜尋
這應該是本題的正解
- c++版本
\#include<cstdio> #include<algorithm> using namespace std; char f[10005][10005]; int p; int dfs(int x,int y){ if(x==0) return 1; if(y==0) return 2; if(f[x][y]!=0) return f[x][y]; f[x][y]=3; f[x][y]=dfs(y,(x+y)%p); if(f[x][y]!=3) f[x][y]^=3; return f[x][y]; } int main(){ int T; scanf("%d%d",&T,&p); while(T--){ int x,y; scanf("%d%d",&x,&y); int d=dfs(x,y); if(d==3) puts("error"); else printf("%d\n",d); } return 0; }
- pascal 版本
var i,s,j,m,n,x,y:longint; f:array[0..10000,0..10000] of shortint; function find(x,y:longint):longint; var i,j:longint; begin inc(s); if f[x,y]=-1 then exit(-2); if f[x,y]<>0 then exit(f[x,y]); f[x,y]:=-1; if x=0 then exit(1); if y=0 then exit(2); find:=find((x+y) mod m,(y+x+y) mod m); if find=-2 then begin f[x,y]:=-2;exit(-2);end else begin f[x,y]:=find;exit(find);end; end; begin read(n,m); for i:=1 to n do begin read(x,y); j:=find(x,y); if j=-2 then writeln('error') else writeln(j); end; end.
(法二) 玄學猜想
- pascal
var i,j,k,n,m,s,t,x,y:longint; a:array[1..233333]of longint; begin readln(n,m); for i:=1 to n do begin readln(x,y); t:=0; while (x<>m)and(y<>m) do begin x:=x mod m+y mod m; y:=y mod m+x mod m; inc(t); if t=10000 then break; end; if x=m then writeln(1); if y=m then writeln(2); if t=10000 then writeln('error'); end; end.
T2 玩遊戲(game)
\#include <stdio.h> #include <iostream> #include <cstring> using namespace std; int n,m,T,pd; int c[500005],a[505][505]; int lowbit(int x){return x&(-x);} void add(int x) { while (x<=n*m) { c[x]+=1; x+=lowbit(x); } } int sum(int x) { int summ=0; while(x>0) { summ+=c[x]; x-=lowbit(x); } return summ; } int main() { // freopen("game6.in","r",stdin); // freopen("game6.out","w",stdout); scanf("%d",&T); while (T) { memset(c,0,sizeof(c)); T--; scanf("%d %d",&n,&m); int sb=n*m,ans=0; for (int i=n;i>0;i--) { if(i%2==1) { for (int j=m;j>0;j--) if (i!=n || j!=m) { ans+=sum((i-1)*m+j); add((i-1)*m+j); } } else if (i%2==0) { for (int j=1;j<=m;j++) if (i!=n || j!=m) { ans+=sum((i-1)*m+j); add((i-1)*m+j); } } }//printf("%d\n",ans); if (ans%2==0) pd=0;else pd=1; for (int i=1;i<=n;i++) { for (int j=1;j<=m;j++) scanf("%d",&a[i][j]); } memset(c,0,sizeof(c));ans=0; for (int i=n;i>0;i--) { if(i%2==1) { for (int j=m;j>0;j--) if (a[i][j]!=0) { ans+=sum(a[i][j]); add(a[i][j]); } } else if (i%2==0) { for (int j=1;j<=m;j++) if (a[i][j]!=0) { ans+=sum(a[i][j]); add(a[i][j]); } } } //printf("%d\n",ans); if (ans%2==pd) printf("1\n");else printf("0\n"); } return 0; }
T3 傻逼症(sbvirus)
\#include<cstdio> #include<algorithm> using namespace std; const int MOD=998244353,MAX_N=5+1e5; typedef long long ll; class Matrix{ ll a[3][3]; public: ll* operator[](int x){ return a[x]; } Matrix(){} Matrix(ll b[3][3]){ for(int i=0;i<3;++i) for(int j=0;j<3;++j) a[i][j]=b[i][j]; } Matrix operator*(Matrix b){ Matrix z; for(int i=0;i<3;++i) for(int j=0;j<3;++j) z[i][j]=0; for(int i=0;i<3;++i) for(int k=0;k<3;++k) if(a[i][k]>0){ for(int j=0;j<3;++j) z[i][j]=(z[i][j]+a[i][k]*b[k][j])%MOD; } return z; } Matrix operator+(Matrix b){ Matrix z; for(int i=0;i<3;++i) for(int j=0;j<3;++j){ z[i][j]=a[i][j]+b[i][j]; if(z[i][j]>=MOD) z[i][j]-=MOD; } return z; } Matrix& operator=(Matrix b){ for(int i=0;i<3;++i) for(int j=0;j<3;++j) a[i][j]=b[i][j]; return *this; } }; Matrix power[MAX_N],sum[MAX_N]; ll _ie[3][3]={{1,0,0},{0,1,0},{0,0,1}}; ll _ze[3][3]={{0,0,0},{0,0,0},{0,0,0}}; ll _gg[3][3]={{0,1,0},{0,0,1},{0,0,1}}; ll _kk[3][3]={{0,0,1},{0,0,0},{0,0,0}}; Matrix ie(_ie),zero(_ze),gg(_gg),kk(_kk); class SegmentTree{ struct Node{ Matrix key,tag1,tag2; }tree[MAX_N<<2]; void put_tag(int sz,int p,Matrix tag1,Matrix tag2){ tree[p].tag1=tree[p].tag1*tag1; tree[p].tag2=tree[p].tag2*tag1+tag2; tree[p].key=tree[p].key*tag1+sum[sz]*tag2; } void down(int l,int r,int p){ int mid=(l+r)>>1; put_tag(mid-l+1,p+p,tree[p].tag1,tree[p].tag2); put_tag(r-mid,p+p+1,tree[p].tag1,power[mid-l+1]*tree[p].tag2); tree[p].tag1=ie,tree[p].tag2=zero; } void up(int p){ tree[p].key=tree[p+p].key+tree[p+p+1].key; } public: void build(int p,int l,int r){ tree[p].key=zero,tree[p].tag1=ie,tree[p].tag2=zero; if(l==r) return; int mid=(l+r)>>1; build(p+p,l,mid); build(p+p+1,mid+1,r); } void change(int p,int l,int r,int x,int y,Matrix tag1,Matrix tag2){ if(l==x&&r==y){ put_tag(r-l+1,p,tag1,tag2); return; } down(l,r,p); int mid=(l+r)>>1; if(y<=mid) change(p+p,l,mid,x,y,tag1,tag2); else if(x>mid) change(p+p+1,mid+1,r,x,y,tag1,tag2); else { change(p+p,l,mid,x,mid,tag1,tag2); change(p+p+1,mid+1,r,mid+1,y,tag1,power[mid-x+1]*tag2); } up(p); } Matrix query(int p,int l,int r,int x,int y){ if(l==x&&r==y) return tree[p].key; down(l,r,p); int mid=(l+r)>>1; if(y<=mid) return query(p+p,l,mid,x,y); else if(x>mid) return query(p+p+1,mid+1,r,x,y); else return query(p+p,l,mid,x,mid)+query(p+p+1,mid+1,r,mid+1,y); } }seg; Matrix base,boost; int getint(){ int ret=0; char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9'){ ret=(ret<<3)+(ret<<1)+c-'0'; c=getchar(); } return ret; } int n,m,c,d; int main(){ n=getint(),m=getint(),c=getint(),d=getint(); base[0][0]=d+1,base[0][1]=c, base[0][2]=0; base[1][0]=0, base[1][1]=1, base[1][2]=0; base[2][0]=d+1,base[2][1]=c, base[2][2]=1; boost[0][0]=1,boost[0][1]=c+d,boost[0][2]=0; boost[1][0]=1,boost[1][1]=c+d,boost[1][2]=0; boost[2][0]=0,boost[2][1]=0, boost[2][2]=1; seg.build(1,1,n); power[0]=ie; for(int i=1;i<=n;++i) power[i]=power[i-1]*base; sum[0]=zero; for(int i=1;i<=n;++i) sum[i]=sum[i-1]+power[i]; while(m--){ int opt=getint(); switch(opt){ case 1:{ int i=getint(),k=getint(); if(i-k+1>=1) seg.change(1,1,n,i-k+1,i,ie,ie); else seg.change(1,1,n,1,i,ie,power[k-i]); break; } case 2:{ int i=getint(),k=getint(); seg.change(1,1,n,max(i-k,1),min(i+k,n),gg,zero); break; } case 3:{ int i=getint(),k=getint(); seg.change(1,1,n,max(i-k,1),min(i+k,n),boost,zero); break; } case 4:{ int l=getint(),r=getint(); if(l>r) printf("0\n"); else printf("%lld\n",(kk*seg.query(1,1,n,l,r))[0][1]); break; } case 5:{ int l=getint(),r=getint(); if(l>r) printf("0\n"); else printf("%lld\n",(kk*seg.query(1,1,n,l,r))[0][0]); break; } } } return 0; }