LOG 模擬賽
第一次見尼瑪這麼給數據範圍的……
開考有點困,迷迷糊糊看完了三道題,真的是像老呂說的那樣,一道都不會……
思考T1,感受有點感受,可是太困了,就先碼了暴力,發現打表能夠50分,因而就大力打了一波表……轉身T3,碼出25分的O(n^2)算法,而後不會了……去了T2,碼出35分的O(n^2)bfs,而後不會了……+
考試還有1h+,我又看了一遍三道題,發現並無什麼新的思路,因而決定去想T1,繼續考試一開始的思路——我發現每加一位,必定是在原合法方案的基礎上加的.想到這裏,我就迅速碼出,發現須要寫尼瑪高精度,然而考試還剩下不到10min……我估了一下我如今這題的分——70分,因而決定不打高精度了……
最後70+35+25=130……
後來發現個人T1作法基本就是正解了,只要再寫上一個漂亮的高精度就能夠A掉了……而正解呢,只不過是繼續把性質推了一下而後把個人刷表法變成了填表法……
T3正解是,在個人思路的基礎之上,利用題目給出的隨機樹的條件,把我每次都要從新處理的東西記錄了下來,這樣像動態點分治同樣每次查詢加修改就能夠了……
思惟筆記:I.信息共用以減小處理次數 II.利用隨機
算法筆記:I.隨機樹(咱們假設其爲有根樹)樹高指望log(實際上都快到根號級別了) II.隨機樹(咱們假設其爲有根樹)中,每一個點的子樹樹高與其在整棵樹中的深度的乘積的加和的指望是n^1.5的
T2好神啊……html
雀巢咖啡系列模擬賽 XLI
異化多肽(T1):
看到這個東西還有那個模數就像要去ntt一發,而後開始推式子.
推啊推,推啊推,推出來一個等比數列求和的式子,很開心,感受本身要A題了.
而後快速碼出快速冪ntt+多項式求逆,調完以後,極限數據5s!!!感受藥丸,而後一波卡常,3s左右,就沒再管……
最後90,T了一個點,發現一個很蠢蛋的問題——我湊,我快速冪ntt個蛋啊,mdzz,我要快速冪ntt求的那個多項式在mod x^(n+1)意義下是0啊!!!!感受本身蠢到家了……
因此這題只不過是一個比較裸的多項式求逆,我爲何要快速冪ntt呢?應該就是我多項式這兒太菜了,固然我以爲也有考試的時候腦子不太正常的緣由,還有個人極限思想應該是不太好.
推出這道題的多項式求逆還能夠直接從生成函數的實際意義和遞推式的角度.node
#pragma GCC optimize("O3") #include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=100010; const int P=1005060097; inline int Pow(int x,int y){ int ret=1; while(y){ if(y&1)ret=(LL)ret*x%P; x=(LL)x*x%P,y>>=1; }return ret; } int A[N<<2],ai[N<<2],rev[N<<2],inv[N<<2],len; int n,m; inline void ntt(int *C,int opt){ register int i,j,k,w;int wn,temp; for(i=1;i<len;++i)if(rev[i]>i)std::swap(C[i],C[rev[i]]); for(k=2;k<=len;k<<=1){ wn=Pow(5,(P-1)/k); if(opt==-1)wn=Pow(wn,P-2); for(i=0;i<len;i+=k){ w=1; for(j=0;j<(k>>1);++j,w=(LL)w*wn%P){ temp=(LL)w*C[i+j+(k>>1)]%P; C[i+j+(k>>1)]=(C[i+j]-temp+P)%P; C[i+j]=(C[i+j]+temp)%P; } } } } inline void get_inv(int *a,int *b,int cd){ if(cd==1){b[0]=Pow(a[0],P-2);return;} get_inv(a,b,cd>>1),len=cd<<1; int i,ni=Pow(len,P-2); for(i=1;i<len;++i)rev[i]=(rev[i>>1]>>1)|((i&1)?(len>>1):0); memcpy(A,a,cd<<2),memset(A+cd,0,cd<<2); ntt(A,1),ntt(b,1); for(i=0;i<len;++i)b[i]=(2-(LL)b[i]*A[i]%P+P)%P*(LL)b[i]%P; ntt(b,-1); for(i=0;i<cd;++i)b[i]=(LL)b[i]*ni%P; memset(b+cd,0,cd<<2); } int main(){ scanf("%d%d",&n,&m);int i,x; for(i=1;i<=m;++i) scanf("%d",&x),++ai[x]; ai[0]=P-1,len=1; while(len<=n)len<<=1; get_inv(ai,inv,len); printf("%d\n",(P-inv[n])%P); return 0; }
編碼病毒(T2):
讀懂題以後,哇塞,時限有4s,徹底能夠O(n^2)再來一個1/32的常數用bitset水之啊,而後15min搞好……
最後100,發現正解是比bitset還要水的水fft,就是一個變了一下形的bzoj2194,可是bitset比這玩意好寫到不知道哪裏去了……ios
#include <cstdio> #include <bitset> #include <cstring> #include <algorithm> const int N=100010; std::bitset<N> test,data,temp; int n,m,cost[N],cnt[(1<<22)+10]; char s[N]; int main(){ scanf("%d%d",&m,&n); int full=(1<<m)-1,i,j,ans=0x3f3f3f3f; memset(cost,0x3f,sizeof(cost)),cost[0]=0; for(i=1;i<=full;++i){ cnt[i]=cnt[i-((-i)&i)]+1; cost[i%n]=std::min(cost[i%n],cnt[i]+1); cost[((-i)%n+n)%n]=std::min(cost[((-i)%n+n)%n],cnt[i]); } for(scanf("%s",s),i=0;i<n;++i)data[i]=s[i]=='1'; for(scanf("%s",s),i=0;i<n;++i)test[i]=s[i]=='1'; for(i=0;i<n;++i){ if(i!=0)j=data[0],data>>=1,data[n-1]=j; temp=data^test; j=temp.count(),j=std::min(j,n-j+1); ans=std::min(ans,j+cost[i]); } printf("%d\n",ans); return 0; }
#include <cmath> #include <cstdio> #include <cstring> #include <complex> #include <algorithm> typedef double db; typedef std::complex<db> cd; const int N=300010; const db Pi=std::acos(-1.); cd A[N<<2],B[N<<2]; int test[N<<2],data[N<<2],ci[N<<2],rev[N<<2],len; int n,m,cost[N],cnt[(1<<22)+10]; char s[N]; inline void fft(cd *C,int opt){ register int i,j,k;cd temp; for(i=1;i<len;++i) if(rev[i]>i) std::swap(C[rev[i]],C[i]); for(k=2;k<=len;k<<=1){ cd wn(std::cos(2*opt*Pi/k),std::sin(2*opt*Pi/k)); for(i=0;i<len;i+=k){ cd w(1.,0.); for(j=0;j<(k>>1);++j,w*=wn){ temp=w*C[i+j+(k>>1)]; C[i+j+(k>>1)]=C[i+j]-temp; C[i+j]+=temp; } } } } inline void mul(int *a,int *b,int *c,int n){ len=1;while(len<n)len<<=1;int i; for(i=0;i<len;++i){ rev[i]=(rev[i>>1]>>1)|((i&1)?(len>>1):0); A[i]=a[i],B[i]=b[i]; } fft(A,1),fft(B,1); for(i=0;i<len;++i)A[i]*=B[i]; fft(A,-1); db inv=1./len; for(i=0;i<len;++i)c[i]=round(A[i].real()*inv); } int main(){ scanf("%d%d",&m,&n); int full=(1<<m)-1,i,j,ans=0x3f3f3f3f; memset(cost,0x3f,sizeof(cost)),cost[0]=0; for(i=1;i<=full;++i){ cnt[i]=cnt[i-((-i)&i)]+1; cost[i%n]=std::min(cost[i%n],cnt[i]+1); cost[((-i)%n+n)%n]=std::min(cost[((-i)%n+n)%n],cnt[i]); } for(scanf("%s",s),i=0;i<n;++i)data[i]=data[i+n]=s[i]=='1'?1:-1; for(scanf("%s",s),i=0;i<n;++i)test[n-i-1]=s[i]=='1'?1:-1; mul(test,data,ci,n+n+n); for(i=0;i<n;++i){ j=(ci[i+n-1]+n)>>1; ans=std::min(ans,cost[i]+std::min(j+1,n-j)); } printf("%d\n",ans); return 0; }
數論函數簇(T3):
考場上從看這道題到最後,我對這道題的解法基本沒有變——暴力模擬題意……
最後20分……
看到題解發現這題一道神題……
首先,你在暴力模擬題意的時候,會發現,知足a*a=a(mod n),a*b=0(mod n)的a和b就是知足條件的a和b,而後繼續觀察,發現知足a*a=a(mod n)的a會對答案貢獻gcd(a,n).
而後,咱們會發現咱們的瓶頸在於找到知足a*a=a(mod n)的a,咱們能夠把這個式子寫成a*(a-1)=0(mod n),發現gcd(a,a-1)=1,這個時候咱們能夠改變對a要知足的條件的表述——由於a與a-1互質,因此gcd(a,n)與gcd(a-1,n)互質,因此若a知足條件,當且僅當gcd(a,n)與gcd(a-1,n)的乘積爲n,也就是說a與a-1中含有不相交且加起來是完整的n的n的兩部分.
接着,咱們設q爲gcd(a-1,n),p爲gcd(a,n),而且先假設1<=a<=n,那麼a-1從0一直到n-1讓q取到了全部的n的因數,咱們先把gcd(q,n/q)!=1的q所有捨去,那麼剩下了幾種q,對於每種q,若想找到一個存在其對應的p的a,就是找到一個含有其對應的p的a模q餘1,也就是找到一個k,使得kp%q=1,而k的取值爲[1,q],因此對於每一種合法q,都會找到一個互不相同的a,從而作出其對應的p的貢獻.也就是說,若是咱們放在p的角度上觀察,就是對於每個p,只要gcd(p,n/p)=1,就會作出p的貢獻.至此,咱們找到了一個新的計算R值的方法:R[n]=∑(d|n)[gcd(d,n/d)=1]*d.那麼R[n]就至關於n的全部知足另外一半與本身互質的約數和,利用這個咱們顯然能夠用歐拉篩法O(n)獲得全部的R,這樣咱們就獲得了50分.
接下來,咱們利用咱們剛剛提到的式子進行一波反演,大力推一波式子,最後推出來的式子是要1到n^0.5枚舉一個值而且每次對n除這個值的平方向下取整獲得的數進行除法分塊,聽說這樣的複雜度是O(n^0.5logn),反正能夠過……
這道題對於數論推導能力,數論裏許多性質的掌握以及化式子的能力考察得很好,是一道很好的題.算法
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=1000000,P=1005060097,inv2=502530049; #define mod(a) ((a)<P?(a):(a)%P) char mu[N+10]; int prime[N+10],len; bool isnot[N+10]; inline LL calc(LL lim){ LL ret=0; for(register LL i=1,last=1;i<=lim;i=last+1){ last=lim/(lim/i); ret=(ret+mod(i+last)*mod(last-i+1)%P*inv2%P*mod(lim/i)%P)%P; } return ret; } int main(){ register int i,j,k; mu[1]=1; for(i=2;i<=N;++i){ if(!isnot[i])prime[++len]=i,mu[i]=-1; for(j=1;prime[j]*i<=N;++j){ isnot[prime[j]*i]=true; if(i%prime[j]==0){mu[prime[j]*i]=0;break;} mu[prime[j]*i]=-mu[i]; } } LL n;int ans=0; scanf("%lld",&n); for(k=1;(LL)k*k<=n;++k) ans=(ans+mu[k]*k*calc(n/((LL)k*k))%P+P)%P; ans=(ans-(n%P)*((n+1)%P)%P*inv2%P+P)%P; printf("%d\n",ans); return 0; }
No.11省選模擬測試
T1:
有一小點點小思想,就是一個簡單的樹狀數組,就是用差分模擬一下模意義下的區間加和.
T2:
滿腦子dp……
又是一道網絡流思想的貪心,我好像一碰到什麼和網絡流有關的東西就不會……
只要把費用流建圖想清楚,而後用線段樹或者堆或者直接sort模擬一下就能夠了……
網絡流急需增強……個人貪心也好弱啊……
T3:
我寫了一個題解上說的60分作法,然而利用矩陣裏大部分都是空的,加一下矩陣乘法的優化就過了,還挺快.這種作法就是在原矩陣中加入記錄前綴和的部分,在圖的模型中就是引入黑洞點.
正解是利用折半的方法求等比數列,這個思想很不錯,十分值得積累與借鑑.
這個折半和fft那個折半大異小同.小程序
No.12省選模擬測試
T1:
相似PA round1裏的清新簡單小貪心.
T2:
我沒有夢想……
先根據指望的線性性推出遞推式x[i]=x[i+1]+x[i-1]-x[i],而後40分到手……
我到這兒就結束了……
可是實際上加了優化的矩陣乘法是徹底能夠跑出70分的,並且我考試的時候發現的全局循環節也能夠拿到70分,然而我一個都沒打,成了一條沒有夢想的鹹魚……
正解是利用差分,將問題轉化爲置換的冪,而後利用輪換O(n)求出.
這道題對於指望線性性以及差分思想的考察很厲害,並且他對於置換羣的考察也比較巧妙.
這個差分真的好厲害,我以爲這個遞推式與差分的關係須要積累與借鑑,我感受這個題裏x[1],x[n]不變在必定程度上也有一些提示吧.
T3:
原題.數組
No.14省選模擬測試
此次考試的題都很不錯誒……
一開考,看了一遍題.T1,博弈論,湊……T2,容斥計數???T3,數據結構題,好親切啊,從他開始吧,想了半天,樹套樹、分塊都不行,後來想到了一種set維護的莫隊,碼了出來,卡了卡常數,感受50+沒問題,就去想T2了.當時不知道爲何忽然一陣睏意來襲,狀態急劇降低,出去上了一趟廁所,回來繼續想T2的容斥,感受麻煩得要死.忽然級部召喚我,我去了一趟,沒人……回來繼續想,感受好像會用到矩陣樹……忽然級部又召喚我,我又去了一趟,在那裏填了個表,而後迅速回來,發現原本不足時的省選模擬測試被級部佔掉了半個多小時……回來快速看了T1,大概想了一個好像並不對的作法,碼了出來,居然過了大樣例,交之……而後去碼T2的暴力,火速碼完兩個部分分,加起來30,還剩下不到15min,因而開始想下一個部分分,仍是想容斥,仍是以爲很麻煩,最後也沒想出來……
最後80+30+20=130
T1,莫名奇妙地80,這個東西的思路就是狀態轉移.有兩種方法,第一種是從已知狀態向後逆轉移,十分清真好理解,第二種是對於每一種狀態去搜索其能到達的全部狀態從而轉移,這種方法十分玄學,並且還容易錯,我如今也不是很懂他那個謎同樣的環的處理辦法……
個人博弈論好弱啊……網絡
#include <cstdio> #include <cstring> #include <algorithm> #define read(a) (scanf("%d",&a)) const int N=7010; int n,st[2][N],degree[2][N],f[2][N],q[N<<1],front,back; int main(){ read(n); register int i; for(int id=0;id<2;++id){ read(st[id][0]); for(i=1;i<=st[id][0];++i) read(st[id][i]),st[id][i]=n-st[id][i]; for(i=1;i<=n;++i) degree[id][i]=st[id][0]; } f[0][1]=f[1][1]=-1; q[back++]=1,q[back++]=-1; register int x,y,u,v; while(front!=back){ x=q[front++]; if(x<0)y=-x,x=1,u=0; else y=x,x=0,u=1; if(f[x][y]>0) for(i=1;i<=st[u][0];++i){ v=(y+st[u][i]-1)%n+1; if(f[u][v])continue; if(!(--degree[u][v])) f[u][v]=-1,q[back++]=u?-v:v; } else for(i=1;i<=st[u][0];++i){ v=(y+st[u][i]-1)%n+1; if(f[u][v])continue; f[u][v]=1,q[back++]=u?-v:v; } } for(int id=0;id<2;++id){ for(i=2;i<=n;++i){ if(f[id][i]==1)printf("Win "); else if(f[id][i]==-1)printf("Lose "); else printf("Loop "); } puts(""); } return 0; }
T2,我沒想出來的那個部分分徹底能夠用補集轉化加矩陣樹解決……正解好神啊,矩陣樹加生成函數.結合基爾霍夫矩陣的那個結論,咱們求出來的主子式行列式就是其全部生成樹的邊權之積的和.那麼若是把原樹邊的值設爲1,把非原樹邊的值設爲x,那麼咱們的主子式行列式是一個多項式,其前k+1項的和,就是咱們的答案.而直接這樣求不是很好,那麼咱們就帶入n個值求出對應的答案,利用拉格朗日插值或者高斯消元解方程組來獲得多項式係數.
補集轉化還真是妙啊……
矩陣樹和多項式都是我理解不夠深入的知識點,像利用矩陣樹計數,利用生成函數計數,都是我比較弱的地方,拉格朗日插值仍是我現學的(現背結論的)……數據結構
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=85; const int P=1000000007; inline int Pow(int x,int y){ int ret=1; while(y){ if(y&1)ret=(LL)ret*x%P; x=(LL)x*x%P,y>>=1; } return ret; } inline int Inv(int x){ return Pow(x,P-2); } int n,cao,v[N][N],g[N],co[N],ans[N]; struct Matrix{ int a[N][N]; inline void clear(){ memset(a,0,sizeof(a)); } inline void add(int x,int y,int z){ a[x][x]+=z,a[y][y]+=z; a[x][y]-=z,a[y][x]-=z; } inline int kir(){ int ret=1,b,inv; register int i,j,k; for(i=1;i<n;++i) for(j=1;j<n;++j) a[i][j]=(a[i][j]%P+P)%P; for(i=1;i<n;++i){ if(a[i][i]==0){ for(j=i+1;j<n;++j) if(a[j][i]){ for(k=i;k<n;++k) std::swap(a[i][k],a[j][k]); break; } ret=(P-ret)%P; } inv=Inv(a[i][i]); for(j=i+1;j<n;++j){ b=(LL)a[j][i]*inv%P; for(k=i;k<n;++k) a[j][k]=(a[j][k]-(LL)b*a[i][k]%P+P)%P; } ret=(LL)ret*a[i][i]%P; } return ret; } }Kir; int main(){ scanf("%d%d",&n,&cao); register int i,j,k; for(i=1;i<n;++i){ scanf("%d",&j); v[i][j]=v[j][i]=1; } for(i=0;i<n;++i){ if(i==0){ co[i]=1; continue; } Kir.clear(); for(j=0;j<n;++j) for(k=j+1;k<n;++k) Kir.add(j,k,v[j][k]?1:i); co[i]=Kir.kir(); } int b,c,l; for(i=0;i<n;++i){ memset(g,0,sizeof(g)); b=co[i],l=1,g[0]=1; for(j=0;j<n;++j){ if(j==i)continue; b=(LL)b*Inv((i-j+P)%P)%P; c=(P-j)%P; for(k=l;k>0;--k) g[k]=((LL)g[k]*c%P+g[k-1])%P; g[0]=(LL)g[0]*c%P; ++l; } for(j=0;j<n;++j) ans[j]=(ans[j]+(LL)g[j]*b)%P; } int answer=0; for(i=0;i<=cao;++i) answer=(answer+ans[i])%P; printf("%d\n",answer); return 0; }
T3,這題真好,只不過個人那30分呢,明明本機能夠50的啊……之後仍是把本機測試速度看成參考吧,實際複雜度纔是最能說明問題的,畢竟評測環境不一樣,速度也不一樣嘛.在個人作法裏把我用set搞的東西所有用鏈表處理出來就能夠70了,再稍微卡卡常數(彷佛還有分塊排序這種卡常操做)就能夠A掉了……利用KD_tree,把詢問看做點,每一個點看做對一個矩形的操做,應該就能50分,可是若是把點按照從大到小的順序進行操做,再加上一些打標記的技巧,會更加得穩……正解的話,好神啊……我感受這題在前兩個部分分里加入權值隨機的條件就有一些暗示……咱們從點對的角度觀察性質,或者說從一個點的貢獻的角度,就會發現對於一個點i,大於它的j,可能和他作出貢獻的j的a必定是單調的,並且對於值大於他的和值小於他的j,單調性不一樣.只是利用這個性質話,能夠在隨機權值中get到50分,由於聽說隨機權值下,對於一個點,他後面的點單調起來的個數是logw的.咱們繼續觀察,同時強化咱們的式子,就能夠獲得題解的最終的作法,十分好打.
這才叫數據結構題啊……
感受用鏈表預處理前驅後繼以代替平衡樹是一種十分優秀的作法,很值得積累與借鑑……
還有這個KD_tree的姿式也是很牛逼……
正解就更不用說了,對着數據結構題推式子,利用權值,再加上利用約束的單調性等等……真是厲害啊……ide
#include <set> #include <cstdio> #include <cstring> #include <algorithm> char xB[(1<<15)+10],*xS,*xT; #define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) template <typename _t> inline void read(_t &x){ register char ch=gtc;bool ud=false; for(x=0;ch<'0'||ch>'9';ch=gtc)if(ch=='-')ud=true; for(;ch>='0'&&ch<='9';x=(x<<1)+(x<<3)+ch-'0',ch=gtc); if(ud)x=-x; } typedef long long LL; typedef std::multiset<int> msi; typedef std::multiset<int>::iterator mit; const int L=150; const int C=100010; const int N=100010; const int M=1000010; const int oo=1000000000; const int Inf=0x7f7f7f7f; msi st; mit re[L<<1]; int len; int n,m,ans[M],a[N],pos[N],cnt,min; struct Q{ int l,r,id; }q[M]; inline bool comp(Q aa,Q bb){ return pos[aa.l]<pos[bb.l]||(pos[aa.l]==pos[bb.l]&&aa.r<bb.r); } inline mit ins(int key){ mit ret,tmp,it; ret=tmp=it=st.insert(key); ++tmp; min=std::min(min,std::abs(key-*tmp)); --it; min=std::min(min,std::abs(key-*it)); return ret; } inline int force_query(int l,int r){ if(r-l+1<=50){ register int i,j,ret=Inf; for(i=l;i<=r;++i) for(j=i+1;j<=r;++j) ret=std::min(ret,std::abs(a[i]-a[j])); return ret; } min=Inf,st.clear(); st.insert(Inf),st.insert(oo-Inf); for(register int i=l;i<=r;++i) ins(a[i]); return min; } inline int round_query(int aim){ int p=pos[aim]*L+1,ret=min; len=0; while(p!=aim) re[++len]=ins(a[--p]); std::swap(ret,min); for(int i=1;i<=len;++i) st.erase(re[i]); return ret; } inline void quick_query(int l,int r){ min=Inf,st.clear(); st.insert(Inf),st.insert(oo-Inf); register int i,p=pos[q[l].l]*L; for(i=l;i<=r;++i){ while(p!=q[i].r)ins(a[++p]); ans[q[i].id]=round_query(q[i].l); } } int main(){ read(n); register int i,l,r; for(i=1;i<=n;++i) read(a[i]),pos[i]=(i-1)/L+1; read(m); for(i=1;i<=m;++i){ read(l),read(r); if(r-l+1<=L)ans[i]=force_query(l,r); else q[++cnt]=(Q){l,r,i}; } std::sort(q+1,q+(cnt+1),comp); for(l=1,r=l;l<=cnt;l=r+1,r=l){ while(r+1<=cnt&&pos[q[l].l]==pos[q[r+1].l])++r; quick_query(l,r); } for(i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }
#include <cstdio> #include <cstring> #include <algorithm> char xB[(1<<15)+10],*xS,*xT; #define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) template <typename _t> inline void read(_t &x){ register char ch=gtc;bool ud=false; for(x=0;ch<'0'||ch>'9';ch=gtc)if(ch=='-')ud=true; for(;ch>='0'&&ch<='9';x=(x<<1)+(x<<3)+ch-'0',ch=gtc); if(ud)x=-x; } const int L=100,N=100010,M=1000010,oo=1000000000,Inf=0x7f7f7f7f; #define del(i) (lq[lh[a[i]]]=lq[a[i]],lh[lq[a[i]]]=lh[a[i]]) #define ins(i) (lq[lh[a[i]]]=a[i],lh[lq[a[i]]]=a[i]) #define get(i) (val[i]=std::min(std::abs(key[lq[a[i]]]-key[a[i]]),std::abs(key[lh[a[i]]]-key[a[i]]))) int n,a[N],key[N],temp[N],pos[N]; int lq[N],lh[N]; int val[N],min[N]; inline void Init(){ for(register int i=1;i<=n;++i) lq[i]=i-1,lh[i]=i+1; lh[0]=1,lq[n+1]=n; } inline bool comp1(int x,int y){return a[x]<a[y];} struct Q{int l,r,id;}q[M]; int m,ans[M],cnt; inline bool comp2(Q aa,Q bb){ return pos[aa.l]<pos[bb.l]||(pos[aa.l]==pos[bb.l]&&aa.r>bb.r); } inline int force_query(int l,int r){ register int i,len=0,ret=Inf; for(i=l;i<=r;++i) temp[++len]=key[a[i]]; std::sort(temp+1,temp+(len+1)); for(i=1;i<len;++i) ret=std::min(ret,temp[i+1]-temp[i]); return ret; } inline void quick_query(int l,int r){ memset(min,0x7f,sizeof(min)); register int i,j,p=pos[q[l].l]*L,wq=n,tmp; Init(); for(i=1;i<=p;++i)del(i); for(i=n;i>p;--i)get(i),del(i); for(i=p+1;i<=n;++i)min[i]=std::min(min[i-1],val[i]); Init(); for(i=1;i<=p-L;++i)del(i); for(i=l;i<=r;++i){ while(wq!=q[i].r)del(wq),--wq; tmp=min[q[i].r]; for(j=p-L+1;j<=p;++j)get(j),del(j); for(j=p;j>p-L;--j)ins(j); for(j=p;j>=q[i].l;--j)tmp=std::min(tmp,val[j]); ans[q[i].id]=tmp; } } int main(){ read(n); register int i,l,r; for(i=1;i<=n;++i)read(a[i]),pos[i]=(i-1)/L+1,temp[i]=i; std::sort(temp+1,temp+(n+1),comp1); for(i=1;i<=n;++i)key[i]=a[temp[i]],a[temp[i]]=i; key[0]=oo-Inf,key[n+1]=Inf; read(m); for(i=1;i<=m;++i){ read(l),read(r); if(pos[l]==pos[r])ans[i]=force_query(l,r); else q[++cnt]=(Q){l,r,i}; } std::sort(q+1,q+(cnt+1),comp2); for(l=1,r=l;l<=cnt;l=r+1,r=l){ while(r+1<=cnt&&pos[q[l].l]==pos[q[r+1].l])++r; quick_query(l,r); } for(i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }
#include <cstdio> #include <cstring> #include <algorithm> #define R register char xB[(1<<15)+10],*xS,*xT; #define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) inline void read(int &x){ R char ch=gtc; for(x=0;ch<'0'||ch>'9';ch=gtc); for(;ch>='0'&&ch<='9';x=(x*10)+ch-'0',ch=gtc); } const int N=100010,M=N*10,oo=1e9,Inf=0x7f7f7f7f; struct Segment_Tree{ Segment_Tree *ch[2]; int min; Segment_Tree(){ch[0]=ch[1]=NULL,min=Inf;} inline void* operator new(size_t); }*C,*mempool,*root; inline void* Segment_Tree::operator new(size_t){ if(C==mempool){ C=new Segment_Tree[(1<<17)+10]; mempool=C+(1<<17)+10; } return C++; } #define mid ((l+r)>>1) inline void insert(Segment_Tree *&p,int l,int r,int pos,int key){ if(!p)p=new Segment_Tree; p->min=std::min(p->min,key); if(l==r)return; if(pos<=mid)insert(p->ch[0],l,mid,pos,key); else insert(p->ch[1],mid+1,r,pos,key); } inline int query(Segment_Tree *p,int l,int r,int z,int y){ if(!p)return Inf; if(z<=l&&r<=y)return p->min; int ret=Inf; if(z<=mid)ret=std::min(ret,query(p->ch[0],l,mid,z,y)); if(mid<y)ret=std::min(ret,query(p->ch[1],mid+1,r,z,y)); return ret; } int n,m,ans[M],tree[N],a[N]; struct Qu{int l,r,id;}q[M]; inline bool comp(Qu aa,Qu bb){return aa.l<bb.l;} inline void U(R int pos,int key){ for(;pos<=n;pos+=pos&(-pos)) tree[pos]=std::min(tree[pos],key); } inline int Q(R int pos){ R int ret=Inf; for(;pos>0;pos-=pos&(-pos)) ret=std::min(ret,tree[pos]); return ret; } inline void work(){ root=NULL; memset(tree,0x7f,sizeof(tree)); std::sort(q+1,q+(m+1),comp); R int i,get,pos=m; for(i=n;i>0;--i){ get=query(root,0,oo,a[i],oo); while(get<=n){ U(get,a[get]-a[i]); get=query(root,0,oo,a[i],((a[i]+a[get])>>1)-1); } insert(root,0,oo,a[i],i); while(pos&&q[pos].l==i) ans[q[pos].id]=std::min(ans[q[pos].id],Q(q[pos].r)),--pos; } } inline void rev(){ std::reverse(a+1,a+n+1); for(R int i=1;i<=m;++i){ q[i].l=n-q[i].l+1,q[i].r=n-q[i].r+1; std::swap(q[i].l,q[i].r); } } int main(){ read(n); R int i,l,r; for(i=1;i<=n;++i)read(a[i]); read(m); for(i=1;i<=m;++i){ read(l),read(r); q[i]=(Qu){l,r,i}; ans[i]=Inf; } work(),rev(),work(); for(i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }
對於本次考試的經驗教訓的話:
I.傍若無人,不要受周圍人的影響
II.數據結構題,速戰速決,不要讓他佔用太多的時間
III.想盡辦法提高考試狀態函數
鍋++模擬賽
雖然鍋了不少題,比賽完也沒有題解,可是這套題仍是很6666666666666666666
平常先看一遍題.T1,有點PA的感受,不太會.T2,什麼鬼.T3,卡特蘭??
決定先看T3,忽然收到消息,兩個樣例都錯了,woc,這題不會還改吧.隨後去看T2,想到了30分作法,感受另外30分有料,可是想去先看一發T1.一看,沒什麼思路,看到了鏈的分,記得是lis,證了一下,果真是,怎麼推廣到樹上呢?繼續觀察,發現題意就是讓我把這個樹變成小根堆啊,那我就是求最大點數小根堆?證了一下,果真是,興奮,想到了一種線段樹合併的打法,迅速碼出,一拍,各類bug,最後發現整個思路都是錯的,繼續思考,試圖挽回,通過了長時間的思考,再加上時間不等人,我就摸索着從新碼,一邊碼,一邊想,一邊改,最後碼了出來,眼動查錯一波,開始拍,拍上了!!!一看錶,尼瑪還剩不到一小時,我剩下倆題還沒管呢,因而迅速碼出T2的30分作法,過了大樣例,而後滾到T3,推出來一個50分的式子,迅速碼完過了兩個樣例,還剩不到2min,把題交上,長吁一口氣……
最後100+30+50=180分
T1用的時間太長了,正解1k,我4k.我打了一個線段樹啓發式合併,個人題解+Kod:
解法:線段樹啓發式合併
結合10分的鏈的作法,咱們能夠像求lis同樣求一個最大點數小根堆.
咱們先說一下40分的dp,f[i][j]表示以i爲根的子樹裏造成的小根堆堆頂權值大於等於j的小根堆點數最大值,而後dp轉移的時候,把兒子的f數組對應位加和,再把選本身產生的貢獻算入便可.
咱們考慮用數據結構優化這一過程,咱們發現對於「以i爲根的子樹裏造成的小根堆堆頂權值」只多是"以i爲根的子樹中點的權值",那麼咱們利用這一點,用線段樹存儲「以i爲根的子樹裏造成的小根堆堆頂權值」以及每一個權值對應的小根堆點數最大值,可是咱們發現咱們這樣作,對於上面那個dp轉移的過程是很難在一個可行時間複雜度內實現的.咱們考慮改變存儲方式,咱們利用線段樹存儲「以i爲根的子樹裏造成的小根堆堆頂權值」以及每一個權值對應的"堆頂權值大於等於他的小根堆點數最大值",這個時候,咱們在實現上述dp轉移過程時,能夠利用啓發式合併,把點數較小的線段樹暴力還原爲數組集合,而後再利用這個數組去更新點數較大的線段樹,最後把選本身產生的貢獻算入.咱們dfs整棵樹同時啓發式合併,最後獲得的根的線段樹裏的全部權值的"堆頂權值大於等於他的小根堆點數最大值"的最大值就是咱們答案.
時間複雜度O(nlog^2n).
#include <cstdio> #include <cstring> #include <algorithm> #define R register char xB[(1<<15)+10],*xS,*xT; #define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) inline void read(int &x){ R char ch=gtc; for(x=0;ch<'0'||ch>'9';ch=gtc); for(;ch>='0'&&ch<='9';x=(x*10)+ch-'0',ch=gtc); } const int N=200010; struct V{ int to,next; }c[N<<1]; int head[N],t; inline void add(int x,int y){ c[++t].to=y,c[t].next=head[x],head[x]=t; } int keep[N],kk[N],len; struct Segment_Tree{ Segment_Tree *ch[2]; int max; Segment_Tree(){ch[0]=ch[1]=NULL,max=0;} inline void pushdown(){ if(!max)return; if(ch[0])ch[0]->max+=max; if(ch[1])ch[1]->max+=max; max=0; } inline void* operator new(size_t); }*root[N],*C,*mempool; inline void* Segment_Tree::operator new(size_t){ if(C==mempool){ C=new Segment_Tree[(1<<16)+10]; mempool=C+(1<<16)+10; } return C++; } #define mid ((l+r)>>1) inline void insert(Segment_Tree *&p,int l,int r,int pos,int key){ if(!p)p=new Segment_Tree; if(l==r){p->max=key;return;} p->pushdown(); if(pos<=mid)insert(p->ch[0],l,mid,pos,key); else insert(p->ch[1],mid+1,r,pos,key); } inline void add_up(Segment_Tree *p,int l,int r,int z,int y,int key){ if(!p)return; if(z<=l&&r<=y){p->max+=key;return;} p->pushdown(); if(z<=mid)add_up(p->ch[0],l,mid,z,y,key); if(mid<y)add_up(p->ch[1],mid+1,r,z,y,key); } inline void dfs(Segment_Tree *p,int l,int r){ if(!p)return; if(l==r){keep[++len]=l,kk[len]=p->max;return;} p->pushdown(); dfs(p->ch[0],l,mid); dfs(p->ch[1],mid+1,r); } inline int lb(Segment_Tree *p,int l,int r,int pos){ if(!p)return 0; if(l==r)return p->max; R int ret; p->pushdown(); if(pos<=mid){ ret=lb(p->ch[0],l,mid,pos); if(ret==0&&p->ch[1])ret=lb(p->ch[1],mid+1,r,pos); } else ret=lb(p->ch[1],mid+1,r,pos); return ret; } inline int pb(Segment_Tree *p,int l,int r,int pos){ if(!p)return 0; if(l==r)return p->max; R int ret; p->pushdown(); if(pos<=mid)ret=pb(p->ch[0],l,mid,pos); else{ ret=pb(p->ch[1],mid+1,r,pos); if(ret==0&&p->ch[0])ret=pb(p->ch[0],l,mid,pos); } return ret; } inline int del(Segment_Tree *&p,int l,int r,int pos){ if(!p)return 0; if(l==r){p=NULL;return l;} R int ret; if(pos<=mid)ret=del(p->ch[0],l,mid,pos); else{ ret=del(p->ch[1],mid+1,r,pos); if(ret==0&&p->ch[0])ret=del(p->ch[0],l,mid,pos); } if(p->ch[0]==NULL&&p->ch[1]==NULL)p=NULL; return ret; } int n,m,size[N],a[N],b[N],pos[N]; inline bool comp(int x,int y){return a[x]<a[y];} inline void dfs(int x,int fa){ R int i,j,last,temp; for(i=head[x];i;i=c[i].next){ if(c[i].to==fa)continue; dfs(c[i].to,x); if(size[x]<size[c[i].to])std::swap(root[x],root[c[i].to]); len=0,dfs(root[c[i].to],1,m); size[x]+=size[c[i].to]; for(j=1,last=1;j<=len;++j){ temp=lb(root[x],1,m,keep[j]); if(last!=keep[j])add_up(root[x],1,m,last,keep[j],kk[j]); insert(root[x],1,m,keep[j],kk[j]+temp); last=keep[j]+1; } } i=lb(root[x],1,m,b[x])+1; insert(root[x],1,m,b[x],i); last=b[x]-1; while(last){ temp=pb(root[x],1,m,last); if(temp==0||temp>=i)break; last=del(root[x],1,m,last)-1; } ++size[x]; } int main(){ read(n); R int i,x,y; for(i=1;i<=n;++i) read(a[i]),pos[i]=i; std::sort(pos+1,pos+(n+1),comp); for(i=1;i<=n;++i){ if(i==1||a[pos[i]]!=a[pos[i-1]])++m; b[pos[i]]=m; } for(i=1;i<n;++i){ read(x),read(y); add(x,y),add(y,x); } dfs(1,0); int ans=n-lb(root[1],1,m,1); printf("%d\n",ans); return 0; }
正解超NB啊.
正解的思路和個人同樣,連代碼實現的板塊都同樣,只不過他尼瑪差分了最大值啊,差分了最大值啊!!!
回憶一下個人作法,我維護了線段樹裏信息的單調性,並且合併兩顆線段樹後,新的線段樹仍然知足單調性,那若是咱們差分了後綴的話,也是沒問題的,不過這個時候咱們仍然須要維護單調性,然而實際上,破壞單調性的源頭是出現相同權值,也就是在差分的時候出現了0,這個時候在每次算入本身的貢獻的時候解決就行了.
#include <map> #include <vector> #include <cstdio> #define pb push_back #define ft first #define sd second #define read(a) (scanf("%d",&a)) const int N=200010; int n,a[N]; std::vector<int> g[N]; std::map<int,int> f[N]; inline void dfs(int x,int fa){ for(auto p:g[x]) if(p!=fa){ dfs(p,x); if(f[x].size()<f[p].size())f[x].swap(f[p]); for(auto q:f[p])f[x][q.ft]+=q.sd; } ++f[x][a[x]]; auto it=f[x].find(a[x]); if(it!=f[x].begin()){ --it,--it->sd; if(!it->sd)f[x].erase(it); } } int main(){ read(n); int i,x,y; for(i=1;i<=n;++i)read(a[i]); for(i=1;i<n;++i){ read(x),read(y); g[x].pb(y),g[y].pb(x); } dfs(1,0); int ans=n; for(auto p:f[1])ans-=p.sd; printf("%d\n",ans); return 0; }
思惟筆記:
I.從鏈推廣到樹是一用由簡單到複雜的思考方式,十分值得借鑑.
II.利用單調性與維護單調性的方式,是一個值得思考的事情,
III.差分彷佛與單調性有什麼聯繫.
IV.差分解決問題好像十分有效,以前遇到過的差分異或、差分轉置換、樹上差分、差分機率等等,都十分巧妙.
算法筆記:
I.map、set的swap能夠O(1)交換兩個容器裏的所有內容.
II.神奇的auto,我並不會用.
*III.排序的啓發式合併會變快?
T2考試的時候沒仔細想,賽後想一想另外30分,並不難,在有了另外30分的思想基礎後,正解也並不難……
這種思想算什麼?權值排序後按權值增量構造?瞎搞?總之,從易到難的思想是有的.
#include <cstdio> #include <cstring> #include <algorithm> #define R register typedef long long LL; char xB[(1<<15)+10],*xS,*xT; #define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) inline void read(int &x){ R char ch=gtc; for(x=0;ch<'0'||ch>'9';ch=gtc); for(;ch>='0'&&ch<='9';x=(x*10)+ch-'0',ch=gtc); } const int N=200010; const int P=1000000007; const int inv2=500000004; struct Seg{ int l,r,key; }seg[N]; inline bool comp(Seg a,Seg b){ return a.key<b.key; } int n,m,size,a[N],pr[N],nt[N],cnt[N],sum,ji[N],ou[N]; #define SUM(a) ((LL)(a)*((a)+1)/2) inline void insert(int l,int r){ R int z=l,y=r,len; if(pr[l-1]!=-1){ z=pr[l-1],len=l-pr[l-1]; sum=(sum-ji[len]+P)%P; } if(nt[r+1]!=-1){ y=nt[r+1],len=nt[r+1]-r; sum=(sum-ji[len]+P)%P; } len=y-z+1; pr[y]=z,nt[z]=y; sum=(sum+ji[len])%P; } inline int modify(){ R LL ret=sum; if(nt[1]!=-1&&pr[n]!=-1){ R int l,r; l=nt[1],r=n-pr[n]+1; ret=ret-ji[l]-ji[r]+SUM(l); if((--l)>r)std::swap(l,r); r=l+r,l=r-l-l; ret=ret+ou[r]-ou[l]+ji[l]; ret%=P; } return ret; } int main(){ read(n); R int i,j,k; ji[0]=0,ou[0]=0,ou[1]=1; for(i=1;i<=n;++i)read(a[i]),ji[i]=(ji[i-1]+SUM(i))%P; for(i=2;i<=n;++i)ou[i]=(ou[i-2]+SUM(i))%P; memset(pr,-1,sizeof(pr)); memset(nt,-1,sizeof(nt)); for(i=1,j=1;i<=n;i+=j,j=1){ while(i+j<=n&&a[i+j]==a[i])++j; seg[++m]=(Seg){i,i+j-1,a[i]}; } std::sort(a+1,a+(n+1)); std::unique(a+1,a+(n+1)); std::sort(seg+1,seg+(m+1),comp); for(i=1,j=1;i<=m;i+=j,j=1){ while(i+j<=m&&seg[i+j].key==seg[i].key)++j; if(i+j>m)break; ++size; for(k=i;k<i+j;++k) insert(seg[k].l,seg[k].r); cnt[size]=modify(); } ++size; cnt[size]=(LL)n*(n+1)/2%P; cnt[size]=(LL)cnt[size]*(cnt[size]+1)%P*inv2%P; R int ans=0; for(i=1;i<=size;++i) ans=(ans+(LL)(cnt[i]-cnt[i-1]+P)*a[i])%P; printf("%d\n",ans); return 0; }
T3考試的時候沒深刻推,其實深刻推也不會發生更多的事情……
正解尼瑪數位dp,這個神奇的special數位dp是去數位dp了Lucas的每一位.
首先,推式子是必須的,不管你是否轉換了座標系,你只要按照組合意義去化簡式子,你就獲得了一個看起來十分清真的式子.
而後,你發現這個式子是若干個兩個組合數相乘的和,利用Lucas之後,是mod進制下每一位的組合數乘積的乘積,由於mod很小,因此咱們枚舉每一位是什麼,f[i][1/0]表示前i位是否須要借位的貢獻和,咱們最後取f[最高位][0]便可,這樣咱們保證了咱們的枚舉到的數必定小於n+m,而且若是不合法,那麼他的值爲0.
#include <cstdio> typedef long long LL; const LL N=200010,P=100003; LL n,m,jie[P],ni[P],A,B,C,f[5][2]; #define c(n,m) (m>n?0:jie[n]*ni[m]%P*ni[n-m]%P) int main(){ scanf("%lld%lld",&n,&m); A=(n+m)>>1,B=(n-m)>>1,C=A+B; ni[0]=jie[0]=1,ni[P-1]=P-1; LL i,j,num,t,a; for(i=1;i<P;++i)jie[i]=jie[i-1]*i%P; for(i=P-2;i>0;--i)ni[i]=ni[i+1]*(i+1)%P; f[0][0]=1; for(i=1;i<=4;++i){ num=C%P,C/=P,a=A%P,A/=P; for(j=0;j<=a;++j){ f[i][(t=num-j)<0]=(f[i][t<0]+f[i-1][0]*c(a,j)*c((t+(t<0?P:0)),a))%P; f[i][(--t)<0]=(f[i][t<0]+f[i-1][1]*c(a,j)*c((t+(t<0?P:0)),a))%P; } } printf("%lld\n",f[4][0]); return 0; }
筆記:
I.Lucas定理好像除了計算組合數之外,還能將其自己做爲解題的工具.
II.推式子的時候式子的美觀程度也是很重要的.
III.像這道題同樣轉換座標系的方法是值得積累的.
IV.利用組合數自己的性質去刨去卡上界的步驟是很妙的.
V.記錄是否借位彷佛很妙的樣子.
VI.數位dp也能夠很活.
本次考試的經驗教訓:
I.時間分配,速戰速決
II.心態良好,臨危不亂
生日模擬賽
這場模擬賽,考試4小時,改題一天半……
一開考,平常看一遍題,T1,尼瑪樣例解釋什麼鬼,T2,不會,T3,好像有鍋,先不作他,好像又是什麼全場只有我不會的字符串題.
決定先去搞T1,作了幾下,不會,因而開始寫暴搜,想起來昨天晚上剛學了叉積,就想用叉積打check,結果,連推帶打1h+,調出來出來以後,發現,這個爆搜並不能拿到一點分,因而先讓他打着表,去搞T2,推出一個小性質,會了30~40的暴力,碼了出來,過了大樣例,又回去看T1的表,沒什麼規律,因而又打了許多其餘的表,仍然沒有什麼規律,因而一臉絕望的我決定交一個表上去,因此我就讓他在那裏打表,去打T3了.看了看T3,好像只會7分……想起來以前鬼同樣的字符串fft,因而開始想fft,雖然沒有想到fft怎麼打,可是我仍是想到了用兩個數組對應項相乘的方法,最後發現這個能夠推廣到全局,因而碼出來了一個20分左右的暴力,因爲據說數據範圍改了,因此我也不知道我能拿多少分,繼續對着T3YY,想出來了一個kmp作法,彷佛比原來的暴力優秀很多,可是我意識到我並不會打kmp啊,ac自動機也不會打,後綴自動機也忘了,看着考試不剩多少時間,我就先去收了T1的表,而後把T1,T2打理好,交上,而後又開始搞T3,然而到考試結束我也沒有把那個kmp碼出來……
最後20+40+69=129
考試方面的總結:
I.讀題要準,要快.
II.打暴力要快.
III.不要在一個沒什麼規律的表上耗費太多的時間.
IV.不要對一道題投入太少.
V.要有信仰.
VI.考試的時候必定要冷靜,不要把時間複雜度計算錯誤.
VII.基礎算法要掌握牢啊.
T1是個神題.
T2十分巧妙,個人40分暴力就不說了,下一個部分分,也就是70分作法,是推一下式子,發現咱們的pos在先後都會知足一個式子,而且結合原來的暴力得知pos是惟一的,因而枚舉pos,進行O(n^2)的dp,因而總複雜度爲O(n^3).正解是在這個作法的基礎之上進行巧妙的問題轉化,將咱們的貢獻計算公式轉化爲組合問題(染色問題或選取元素問題),從而使得咱們原本的瓶頸(咱們的限制條件對於pos來講必定要從pos向兩邊走,從而使得咱們的dp不得不每次從新開始)得以解決,也就是說咱們只須要進行O(1)次O(n^2)的dp,同時爲了方便計算,正解還引入了一些新的定義,最終使得咱們能夠在O(n^2)的時間內獲得答案.
#include <cstdio> #include <cstring> #include <algorithm> #define read(a) (scanf("%d",&a)) typedef long long LL; const int N=2010; const int P=1000000007; int n,ans,a[N]; int f0[N][N],f1[N][N],g0[N][N],g1[N][N],g2[N][N]; inline void Init(){ read(n); register int i; for(i=1;i<=n;++i) read(a[i]); f0[0][0]=1; g0[n+1][0]=1; } inline void dp(){ register int i,j,k; for(i=1;i<=n;++i) for(j=0;j<=i;++j){ if(a[i]){ k=std::max(2,j+1); f0[i][k]=(f0[i][k]+f0[i-1][j])%P; f1[i][k]=(f1[i][k]+f1[i-1][j])%P; } if(a[i]<=0){ k=std::max(0,j-1); f0[i][k]=(f0[i][k]+f0[i-1][j])%P; f1[i][k]=((LL)f1[i][k]+f0[i-1][j]+f1[i-1][j])%P; } } for(i=n;i>0;--i) for(j=0;j<=(n-i);++j){ if(a[i]){ k=std::max(0,j-1); g0[i][k]=(g0[i][k]+g0[i+1][j])%P; g1[i][k]=((LL)g1[i][k]+g0[i+1][j]+g1[i+1][j])%P; g2[i][k]=((LL)g2[i][k]+g1[i+1][j]*2+g2[i+1][j]+g0[i+1][j])%P; } if(a[i]<=0){ k=j+1; g0[i][k]=(g0[i][k]+g0[i+1][j])%P; g1[i][k]=(g1[i][k]+g1[i+1][j])%P; g2[i][k]=(g2[i][k]+g2[i+1][j])%P; } } } inline void print(){ for(register int i=0;i<=n;++i) ans=(ans+(LL)f0[i][0]*g2[i+1][0]+(LL)f1[i][0]*g1[i+1][0])%P; printf("%d\n",ans); } int main(){ Init(),dp(),print(); return 0; }
筆記:
I.又是一次找限制條件,我又沒發現,從限制條件入手和發掘題目信息彷佛有點像,都很重要啊.
II.個人dp好弱啊……
III.找準問題瓶頸在必定程度上有利於解題.
IV.問題轉化,尤爲是將抽象問題轉化爲組合問題,彷佛很妙的樣子.
V.對於dp狀態,引入新的定義彷佛會帶來新的方向.
T3這題的數據真是水……做爲一隻字符串弱雞,拿到這些分數已經很慶幸了……正解聽說的SAM,可是因爲題解很唬,std更唬,聰明的OIer們就本身YY了另一種解法,不知道和正解比起來誰更優秀,總之是一種時空複雜度應該都合法的作法,反正在現有數據範圍下跑得挺快.個人思路,和我後來想的kmp,以及看到題解以後YY的SAM,都沒法避免每個貢獻都切切實實得去給一個O(1)的時間,這樣,時間複雜度都和個人暴力是同樣的.可是呢,wxh想到了用二分+hash來一次跑多個貢獻,可是因爲串數問題,仍是過不了,不過zzh提到了Trie樹,而後zyf提議Trie樹hash,因此就有了一種O(n*logn*hash_table)時間複雜度的作法,感受很優秀,因而全部人都用這種方法過掉了這題,正解涼涼了……
一個字符串算法的板子都不會,真是夠了……
立刻就要省選了,個人字符串水平很着急啊……
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; typedef unsigned long long ULL; const int N=100010,M=300010,P=1000000007,mod=612497; const ULL K=199; struct Hash_Table{ struct Link{ int next,key; ULL hash; }link[M]; int head[mod],t; void insert(ULL hash,int key){ int pos=hash%mod; link[++t].hash=hash; link[t].key=key; link[t].next=head[pos]; head[pos]=t; } bool count(ULL hash){ for(register int i=head[hash%mod];i;i=link[i].next) if(link[i].hash==hash) return true; return false; } int give(ULL hash){ for(register int i=head[hash%mod];i;i=link[i].next) if(link[i].hash==hash) return link[i].key; } }hash_table; struct Trie{ Trie *ch[5]; int cnt; }node[M],*root; int sz; #define newnode (node+(sz++)) inline void Init(){root=newnode;} char z[N],t[M]; int n,m,a[N],b[N]; ULL zi[N],fi[N],cf[N]; inline char check(char ch){ switch(ch){ case 'A':return 1; case 'C':return 2; case 'G':return 3; case 'T':return 4; } } inline void insert(char *s){ register int i,l=strlen(s+1); register Trie *p=root; for(i=1;i<=l;++i){ s[i]=check(s[i]); if(!p->ch[s[i]])p->ch[s[i]]=newnode; p=p->ch[s[i]],++p->cnt; } } inline void dfs(Trie *p,int sum,ULL hash){ if(!p)return; sum+=p->cnt; if(sum)hash_table.insert(hash,sum); for(int i=1;i<=4;++i) dfs(p->ch[i],sum,hash*K+i); } #define get_hash(a,b,c) (a[c]-a[b-1]*cf[c-b+1]) inline void full_in(int *ai,ULL *ki){ register int i,l,r,mid,ans; for(i=1;i<=n;++i){ l=i,r=n,ans=0; while(l<=r){ mid=(l+r)>>1; if(hash_table.count(get_hash(ki,i,mid)))ans=mid,l=mid+1; else r=mid-1; } if(ans)ai[i]=hash_table.give(get_hash(ki,i,ans)); } } int main(){ Init(); scanf("%s",z+1); n=strlen(z+1); register int i,ans=0; for(i=1;i<=n;++i) z[i]=check(z[i]),zi[i]=zi[i-1]*K+z[i]; cf[0]=1; for(i=1;i<=n;++i) fi[i]=fi[i-1]*K+z[n-i+1],cf[i]=cf[i-1]*K; scanf("%d",&m); for(i=1;i<=m;++i) scanf("%s",t+1),insert(t); dfs(root,0,0); full_in(a,zi),full_in(b,fi); for(i=1;i<=n;++i) ans=(ans+(LL)a[i]*b[n-i+2])%P; printf("%d\n",ans); return 0; }
筆記:
I.哈希Trie樹好像很優秀的樣子,可是我還不是特別清楚這玩意是啥,好像是用來查詢一個串是不是Trie樹前綴的?
II.找到問題瓶頸,並以其爲方向去思考,並努力解決他,是一個不錯的解決問題的方式.
III.hash的時候模數的選取影響效果,進制數的選取仍然影響效果.
IV.字符串匹配問題上數組也是一種不錯的方法.
翻車模擬賽
平常先看一遍題.T1,額,支配樹?T2,多項式?ln?根號?T3,額,讀了好長時間纔讀懂題,碼農題……
回到T1,仔細端詳,誒?成都集訓的時候有這道題?好像仍是YJQ講的,額,回憶一下,好像有什麼反向Inf邊,本身YY了一下,而後手玩了一下,又猜了一發,證了一下,碼了出來,過了樣例,感受一切十分完美.在我離開這題的前一秒,忽然注意到有不合法的狀況,怎麼判呢?直接判最大流是否大於Inf就行了!迅速改完交上,去看T2.這啥玩意啊,幹,好像會個10分……看了一下表,還剩2.5h+,這個碼農題,應該還能幹.因而去搞T3,想了一下LCT維護子樹,一開始我由於看到題目的描述很像LCT的過程,就一直想怎麼在按照他的過程走的時候計算答案,發現這樣彷佛不太可行,因而我就去想用LCT維護子樹來記錄我想記錄的全部信息,發現這太屎了……因而我結合剛剛想到的東西,YY出來一種比較好打的把原問題拆成兩部分的作法,第一部分,沿用我一開始的思路,一邊跟着走,一邊統計子樹信息,第二部分,按照第二次想的思路,不過不用維護子樹,去計算鏈的貢獻.因而決定先碼個暴力出來,而後去碼剛剛的思路,就這樣碼了1h+,隨後,用1h-調試,最終在離考試結束還剩10min的時候,拍上了.交上T3以後迅速去碼T2小暴力,在離考試結束還剩1min的時候,過了小樣例,趕忙交上,而後測了一發大樣例,沒過……
最後10+0+100=110
T1,我TM在迅速碼上判斷無解的部分的時候,忘了改原來的輸出了,結果跑了兩邊dinic,只拿到了無解的分,這和直接輸出-1是一個分啊,湊.
之前我都是強迫症同樣地當心,最近好不容易改過來了,反而成了大意???看來在當心謹慎的程度方面,仍是要有個度的,我要是改完以後,測一發樣例,也不至於10分啊.幸虧這不是省選,長個教訓,在肯定最終提交程序以前,先測試一下啊.
不過,回到這道題自己來,要不是之前考過,我確定不會啊,像這道題這樣的最小割加Inf邊,加反向邊的思路,我仍是沒有掌握好的,省選以前,必定要抽時間搞搞網絡流啊.
個人大意好像還和一時興奮有些關係,之後考試的時候必定要時刻冷靜,不慌,也不要興奮.
#include <cstdio> #include <cstring> #include <algorithm> #define read(a) (scanf("%d",&a)) typedef long long LL; const int N=110; const int M=2510; const LL Inf=1e17; struct V{ int to,next; LL f; }c[M<<2]; int head[N],t=1; inline void add(int x,int y,LL z){ c[++t].to=y,c[t].next=head[x],head[x]=t,c[t].f=z; } int q[N],front,back; int deep[N]; int n,m; inline bool bfs(){ memset(deep,-1,sizeof(deep)); deep[1]=1; front=back=0; q[back++]=1; register int i,x; while(front!=back){ x=q[front++]; for(i=head[x];i;i=c[i].next) if(c[i].f&&deep[c[i].to]==-1){ deep[c[i].to]=deep[x]+1; if(c[i].to==n)return true; q[back++]=c[i].to; } } return false; } inline LL dfs(int x,LL v){ if(x==n||v==0)return v; register LL ret=0,f; for(int i=head[x];i;i=c[i].next) if(c[i].f&&deep[c[i].to]==deep[x]+1){ f=dfs(c[i].to,std::min(c[i].f,v)); ret+=f,v-=f,c[i].f-=f,c[i^1].f+=f; if(!v)break; } if(!ret)deep[x]=-1; return ret; } inline LL dinic(){ LL ret=0; while(bfs())ret+=dfs(1,Inf); return ret; } int main(){ read(n),read(m); register int i,x,y,z; for(i=1;i<=m;++i){ read(x),read(y),read(z); add(x,y,z),add(y,x,Inf); } LL ans=dinic(); if(ans>=Inf)puts("-1"); else printf("%lld\n",ans); return 0; }
T2,個人暴力是錯的,可是我看到的那個10分,實際上是考察的對n==1的特判啊!
這道題把問題轉化爲了二分圖,是啊,這兩個問題徹底等價啊,又是問題的等價轉化,我尼瑪又不會!!!
之後必定要特別地注意模型轉化的思路.
轉爲二分圖之後,就是比較普通的圖論組合數dp——增量構造的同時,欽定固定點,枚舉固定點所在聯通塊大小,進行dp轉移.換個角度來看的話,這其實應該是比較普通的求聯通圖dp值的容斥.
特判拿分不失爲一種好策略.
時間短促的時候,想幾個有理有據的特判,不失爲一種好策略,不過仍是要切合具體狀況和寫小暴力衡量一下.
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=1000; const int P=105225319; const int inv2=52612660; int n,m,mi[N*N+10],c[N+10][N+10],g[N+10],f[N+10],ans[N+10]; int main(){ scanf("%d%d",&n,&m); if(n==1){puts("0");return 0;} register int i,j; c[0][0]=1; for(i=1;i<=n;++i){ c[i][0]=1; for(j=1;j<=i;++j) c[i][j]=(c[i-1][j-1]+c[i-1][j])%P; } mi[0]=1,++m,j=n*n; for(i=1;i<=j;++i) mi[i]=(LL)mi[i-1]*m%P; g[0]=f[0]=ans[0]=1,f[1]=2; for(i=1;i<=n;++i) for(j=0;j<=i;++j) g[i]=(g[i]+(LL)c[i][j]*mi[(i-j)*j])%P; for(i=2;i<=n;++i){ for(j=1;j<i;++j) f[i]=(f[i]+(LL)c[i-1][j-1]*f[j]%P*g[i-j])%P; f[i]=(g[i]-f[i]+P)%P; } for(i=1;i<=n;++i){ f[i]=(LL)f[i]*inv2%P; for(j=1;j<=i;++j) ans[i]=(ans[i]+(LL)c[i-1][j-1]*f[j]%P*ans[i-j])%P; } printf("%d\n",ans[n]); return 0; }
T3,只要知道一些LCT就很好想,有一些小坑的碼農題,聽說jcy有用一個LCT解決的辦法,可是彷佛很屎,不如寫倆(實際上是,寫一個,粘一個).
發現是bzoj原題——bzoj3779:重組病毒.
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=100010; struct V{int to,next;}c[N<<1]; int head[N],t; LL ans1[N]; int n,m,ans2[N],ans3[N],opt[N],poi[N]; char s[100]; inline void add(int x,int y){ c[++t].to=y,c[t].next=head[x],head[x]=t; } namespace work1{ struct splay{ splay *ch[2],*f; int rev; int size_of_void_son; LL ans_of_void_son; int size_of_all_son; LL ans_of_all_son; inline void pushup(){ size_of_all_son=size_of_void_son+ch[0]->size_of_all_son+ch[1]->size_of_all_son; ans_of_all_son=ans_of_void_son+ch[0]->ans_of_all_son+ch[1]->ans_of_all_son; } inline void mark(){ rev^=1; std::swap(ch[0],ch[1]); } inline void pushdown(){ if(!rev)return; ch[0]->mark(); ch[1]->mark(); rev=0; } }*null,node[N]; inline int isroot(splay *p){ return p->f->ch[0]!=p&&p->f->ch[1]!=p; } inline int get(splay *p){ return p->f->ch[1]==p; } inline void dig(splay *p){ if(isroot(p)){ p->pushdown(); return; } dig(p->f); p->pushdown(); } inline void rotate(splay *p){ splay *fa=p->f,*pa=fa->f; int j=get(p); if(!isroot(fa))pa->ch[get(fa)]=p; if((fa->ch[j]=p->ch[j^1])!=null)fa->ch[j]->f=fa; fa->f=p,p->ch[j^1]=fa,p->f=pa; fa->pushup(); p->pushup(); } inline void spaly(splay *p){ dig(p); for(splay *fa=p->f;!isroot(p);rotate(p),fa=p->f) if(!isroot(fa)) rotate(get(fa)==get(p)?fa:p); } inline void expose(splay *x){ splay *y=null; while(x!=null){ spaly(x); x->size_of_void_son-=y->size_of_all_son; x->ans_of_void_son-=y->ans_of_all_son+y->size_of_all_son; y->pushup(); std::swap(x->ch[1],y); x->size_of_void_son+=y->size_of_all_son; x->ans_of_void_son+=y->ans_of_all_son+y->size_of_all_son; y=x; x=x->f; } y->pushup(); } inline void make_root(splay *p){ expose(p),spaly(p),p->mark(); } inline void dfs(int x,int fa){ node[x].f=node+fa; node[x].ch[0]=node[x].ch[1]=null; node[x].size_of_void_son=1; node[x].ans_of_void_son=1; for(int i=head[x];i;i=c[i].next) if(c[i].to!=fa){ dfs(c[i].to,x); node[x].size_of_void_son+=node[c[i].to].size_of_all_son; node[x].ans_of_void_son+=node[c[i].to].ans_of_all_son+node[c[i].to].size_of_all_son; } node[x].pushup(); } inline void Init(){ null=node; null->ch[0]=null->ch[1]=null->f=null; dfs(1,0); } inline void Main(){ Init(); register int i,x; splay *p; for(i=1;i<=m;++i){ switch(opt[i]){ case 1: expose(node+poi[i]); break; case 2: make_root(node+poi[i]); break; case 3: p=node+poi[i],spaly(p); ans1[i]=p->ch[1]->ans_of_all_son+p->ans_of_void_son; ans2[i]=p->ch[1]->size_of_all_son+p->size_of_void_son; break; } } } } namespace work2{ struct splay{ splay *ch[2],*f; int color_mark,rev_mark; int my_color; int left_color,right_color,color_size; inline void pushup(){ left_color=ch[0]->color_size?ch[0]->left_color:my_color; right_color=ch[1]->color_size?ch[1]->right_color:my_color; color_size=1; if(ch[0]->color_size){ color_size+=ch[0]->color_size; if(ch[0]->right_color==my_color)--color_size; } if(ch[1]->color_size){ color_size+=ch[1]->color_size; if(ch[1]->left_color==my_color)--color_size; } } inline void mark_rev(){ rev_mark^=1; std::swap(ch[0],ch[1]); std::swap(left_color,right_color); } inline void mark_color(int color){ my_color=color; left_color=right_color=color; color_size=color_size?1:0; color_mark=color; } inline void pushdown(){ if(rev_mark){ ch[0]->mark_rev(); ch[1]->mark_rev(); rev_mark=0; } if(color_mark){ ch[0]->mark_color(color_mark); ch[1]->mark_color(color_mark); color_mark=0; } } }*null,node[N]; int cnt_color; inline int isroot(splay *p){ return p->f->ch[0]!=p&&p->f->ch[1]!=p; } inline int get(splay *p){ return p->f->ch[1]==p; } inline void dig(splay *p){ if(isroot(p)){ p->pushdown(); return; } dig(p->f); p->pushdown(); } inline void rotate(splay *p){ splay *fa=p->f,*pa=fa->f; int j=get(p); if(!isroot(fa))pa->ch[get(fa)]=p; if((fa->ch[j]=p->ch[j^1])!=null)fa->ch[j]->f=fa; fa->f=p,p->ch[j^1]=fa,p->f=pa; fa->pushup(); p->pushup(); } inline void spaly(splay *p){ dig(p); for(splay *fa=p->f;!isroot(p);rotate(p),fa=p->f) if(!isroot(fa)) rotate(get(fa)==get(p)?fa:p); } inline void expose(splay *x){ splay *y=null; while(x!=null){ spaly(x); x->ch[1]=y; x->pushup(); y=x; x=x->f; } } inline void make_root(splay *p,int color){ expose(p),spaly(p),p->mark_rev(),p->mark_color(color); } inline void dfs(int x,int fa){ node[x].f=node+fa; node[x].ch[0]=node[x].ch[1]=null; node[x].my_color=node[x].left_color=node[x].right_color=++cnt_color; node[x].color_size=1; for(int i=head[x];i;i=c[i].next) if(c[i].to!=fa) dfs(c[i].to,x); } inline void Init(){ null=node; null->ch[0]=null->ch[1]=null->f=null; dfs(1,0); } inline void Main(){ Init(); register int i,x; splay *p; for(i=1;i<=m;++i){ p=node+poi[i]; switch(opt[i]){ case 1: expose(p),spaly(p); p->mark_color(++cnt_color); break; case 2: make_root(p,++cnt_color); break; case 3: expose(p),spaly(p); ans3[i]=p->color_size-1; break; } } } } int main(){ scanf("%d%d",&n,&m); int i,x,y; for(i=1;i<n;++i){ scanf("%d%d",&x,&y); add(x,y),add(y,x); } for(i=1;i<=m;++i){ scanf("%s",s); switch(s[2]){ case 'L':opt[i]=1;break; case 'C':opt[i]=2;break; case 'Q':opt[i]=3;break; } scanf("%d",&poi[i]); } work1::Main(),work2::Main(); for(i=1;i<=m;++i) if(opt[i]==3){ printf("%.10f\n",(double)ans1[i]/ans2[i]+ans3[i]); } return 0; }
UOJ Round #17
第一次打UOJ,沒怎麼上心,全程划水……
T1,對着部分分碼了一發貪心,結果過了全部樣例,出題人在逗我?無論了,就這樣吧.
T2,額,啥玩意,暴力怎麼打,爲何會出現不在點上的狀況……
T3,想了很久,仍然只會10分……
考試結束以前,去碼了一個瞎搞的T2暴力……
最後,25+5+10=40分……
T1的貪心是錯的,給一個樣例:
5
11 12 13 15 19
看完樣例就能知道爲何是錯的了,大該意思能夠感性理解爲,一開始選擇較大,可是1的位對於以後的數來講不「親切」,結果致使最後答案更優.
正解固然不是什麼貪心,是dp啦.
首先,咱們發現咱們的過程實際上就是把這些數的非公共1位逐步變成0,那麼咱們能夠先把公共1位提出來,對於非公共1位的每一個狀態,算出達到其的最小花費,可是使用了重複數字怎麼辦?不可能啊,咱們要的是最小花費啊.這樣咱們有了55分作法.加上以前的貪心,就70了!
而後,咱們考慮優化,咱們能夠對於每次轉移不使用枚舉元素,而是預處理空集狀態是否存在而後枚舉子集,這樣,再加上一些減枝就能夠過了.
在這道題裏,咱們要善於發現dp的正確性,以及貪心的錯誤,這個只須要仔細思考,就像jiry_2說的「事實上的確是但願考驗你們獨立思考、當心求證的能力」.同時,簡潔高效得實現代碼也是在打這道題的貪心的時候獲得的教訓,明明每次sort就能夠,我卻打了堆,慢了很多(這個堆的常數不小嘛,好像插入和查詢沒什麼常數,而刪除的常數是3).在找出貪心的錯誤的時候,目的性地去構造數據,感受是一個不錯的方法,少了許多抽象的證實,可是感受不是老是適用.在後來優化的過程當中,發現轉移的實質以及容許以優充劣的狀況的出現,是這道題體現的好的思想.
#include <cstdio> #include <cstring> #include <algorithm> char xB[(1<<15)+10],*xS=xB,*xT=xB; #define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) inline void read(int &x){ register char ch=gtc; for(x=0;ch<'0'||ch>'9';ch=gtc); for(;ch>='0'&&ch<='9';x=x*10+ch-'0',ch=gtc); } typedef long long LL; const int A=18; const LL Inf=1e16; const int N=100010; const int F=(1<<18)+10; int bin[A],pub,all; int n,a[N],m,st[F],ex[F]; LL ans,f[F]; int main(){ read(n),pub=(1<<A)-1; register int i,j,s; for(i=1;i<=n;++i) read(a[i]),pub&=a[i],all|=a[i]; ans=(LL)pub*n; bin[0]=1; for(i=1;i<A;++i)bin[i]=bin[i-1]<<1; ex[0]=1; all^=pub; for(i=1;i<=n;++i) ex[(a[i]&all)^all]=1; for(i=all;i;i=(i-1)&all){ if(ex[i])continue; for(j=0;j<A;++j) if((all&bin[j])&&(i&bin[j])==0) ex[i]|=ex[i|bin[j]]; } for(i=all;i;i=(i-1)&all) st[++m]=i; f[0]=0; for(i=m;i>0;--i){ s=st[i],f[s]=Inf; for(j=s;j;j=(j-1)&s){ if(f[s]<=(s^j))break; if(ex[j])f[s]=std::min(f[s],f[s^j]+(s^j)); } } ans+=f[all]; printf("%lld\n",ans); return 0; }
T2,就是仔細分析一下題目中描述的運動,設計合理的狀態,而後二分+bfs或者直接spfa來解決它,而分析這個運動以及設計合理狀態,就是這道題的關鍵所在了,這也須要必定的證實正確性,以及意識到錯誤的能力.我在分析題目和設計狀態的方面仍是比較弱的.
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define ft first #define sd second #define mmp(a,b) (std::make_pair(a,b)) typedef long long LL; typedef std::pair<int,int> pii; const int N=1010; const double Inf=1e7; const double eps=1e-6; double dis[N][N],Dis[N][N]; bool fin[N][N]; struct V{ int to,next,id; }c[N<<1]; int head[N],t; inline void add(int x,int y,int z){ c[++t].to=y,c[t].next=head[x],head[x]=t,c[t].id=z; } int n,sx,sy; int a[N],b[N]; int degree[N]; pii queue[N*N],e[N]; int front,back; bool in[N][N]; inline double query(int i,int x,int y){ double ret=std::min(dis[i][x],dis[i][y]); double tx,ty,ki,bi,kei,bei; if(b[x]==b[y])tx=a[i],ty=b[x]; else if(a[x]==a[y])tx=a[x],ty=b[i]; else{ ki=(double)(a[y]-a[x])/(b[x]-b[y]); bi=b[i]-a[i]*ki; kei=(double)(b[x]-b[y])/(a[x]-a[y]); bei=b[x]-a[x]*kei; tx=(bi-bei)/(kei-ki); ty=tx*ki+bi; } if((a[x]<=tx&&tx<=a[y])||(a[y]<=tx&&tx<=a[x])) ret=std::sqrt((tx-a[i])*(tx-a[i])+(ty-b[i])*(ty-b[i])); return ret; } inline bool check(int x,int y,double lim){ return (fin[x][e[y].ft]&&dis[x][e[y].ft]<=lim)||(fin[x][e[y].sd]&&dis[x][e[y].sd]<=lim); } inline bool check(double lim){ front=back=0; memset(in,0,sizeof(in)); register int x,y,i,u,w,q; for(i=head[sx];i;i=c[i].next) if(Dis[sy][c[i].id]<=lim){ in[sy][c[i].id]=true; queue[back++]=mmp(sy,c[i].id); } for(i=head[sy];i;i=c[i].next) if(Dis[sx][c[i].id]<=lim){ in[sx][c[i].id]=true; queue[back++]=mmp(sx,c[i].id); } while(front!=back){ x=queue[front].ft,y=queue[front].sd; front++; w=e[y].ft,q=e[y].sd; for(i=head[x];i;i=c[i].next){ u=c[i].to; if(in[u][y]==false&&Dis[u][y]<=lim){ if(check(u,y,lim))return true; in[u][y]=true; queue[back++]=mmp(u,y); } u=c[i].id; if(in[w][u]==false&&Dis[w][u]<=lim){ if(check(w,u,lim))return true; in[w][u]=true; queue[back++]=mmp(w,u); } if(in[q][u]==false&&Dis[q][u]<=lim){ if(check(q,u,lim))return true; in[q][u]=true; queue[back++]=mmp(q,u); } } } return false; } inline double judge(){ double l=dis[sx][sy],r=Inf,mid; while(l+eps<r){ mid=(l+r)*0.5; if(check(mid))r=mid; else l=mid; } return r; } int main(){ //freopen("rio.in","r",stdin); scanf("%d%d%d",&n,&sx,&sy); register int i,j,x,y; for(i=1;i<=n;++i) scanf("%d%d",&a[i],&b[i]); for(i=1;i<n;++i){ scanf("%d%d",&x,&y); add(x,y,i),add(y,x,i); ++degree[x],++degree[y]; e[i]=mmp(x,y); } for(i=1;i<=n;++i) for(j=1;j<=n;++j){ dis[i][j]=std::sqrt((LL)(a[i]-a[j])*(LL)(a[i]-a[j])+(LL)(b[i]-b[j])*(LL)(b[i]-b[j])); fin[i][j]=(degree[i]==1&°ree[j]==1)||(i==j); } if(sx==sy){puts("0");return 0;} if(fin[sx][sy]){ printf("%.10f\n",dis[sx][sy]); return 0; } for(i=1;i<=n;++i) for(j=1;j<n;++j) Dis[i][j]=query(i,e[j].ft,e[j].sd); printf("%.10f\n",judge()); return 0; }
T3,尚未去搞正解,只能說知道這題在問啥吧.正解是積分,個人積分仍是很弱,在這道題裏,除了打了一個傻暴力之外,最大的收穫就是學了一發神奇的隨機化.學到的地方大概是:屢次隨機去計算指望值,枚舉分數來匹配小數從而解決答案在模意義下的問題,調整精度來調整隨機化結果,一個神奇的隨機數寫法.
對於這道題,咱們的答案是模意義下的,感受不太能隨機化,可是感受起來是個分數,並且分子分母不會太大(至於爲何,我也不清楚),那麼咱們能夠先隨機化小數,而後再枚舉分數,去找距離隨機化結果最近的分數,而後輸出.這個東西在n,m比較大的時候就不適用了,可是最起碼能夠騙到30分啊.
在隨機化的時候,隨機數函數的寫法十分值得學習,並且一開始我把0x7fffffff寫成0x7f7f7f7f致使隨機小數十分不許……
感受這個隨機數函數寫法十分隨機,基本找不到重複數字……
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> #define read(a) (scanf("%d",&a)) namespace RAND{ int seed=45,put_out=0x7fffffff; inline double rando(){ seed^=seed<<5; seed^=seed>>17; seed^=seed<<13; return (double)(seed&=put_out)/put_out; } } typedef long long LL; const int N=30; const int P=998244353; const int T=10000000; const int L=100; const double eps=1e-5; int n,m,a[N*N],b[N*N],inv[L+10]; double f[N]; int main(){ read(n),read(m); register int i,j; inv[1]=1; for(i=2;i<=L;++i)inv[i]=(-(LL)P/i*inv[P%i]%P+P)%P; if(n>10){ printf("%lld\n",(LL)(3*n-2)*inv[2*n]%P); return 0; } for(i=1;i<=m;++i) read(a[i]),read(b[i]); register double sum=0.,max; for(i=1;i<=T;++i){ max=0.; for(j=1;j<=n;++j)f[j]=RAND::rando(),max=std::max(max,f[j]); for(j=1;j<=m;++j)max=std::max(max,f[a[j]]+f[b[j]]); sum+=max; } sum/=T; double min=1e16; int ans; for(i=1;i<=(L<<1);++i){ for(j=1;j<=L;++j) if(std::fabs((double)i/j-sum)<min){ min=std::fabs((double)i/j-sum); ans=(LL)i*inv[j]%P; if(min<eps)break; } if(min<eps)break; } printf("%d\n",ans); return 0; }
No.18省選模擬賽
一開考,平常先看一遍題,T1,貪心?T2,哈夫曼編碼?T3,啥玩意?
決定先去開T1,感受這個貪心不是很難啊,爲了卡常,打了斜堆,碼了出來,發現事情很不對,woc,我是錯的,因而陷入沉思,把第一部分貪心打對,可是第二部分貪心死活不對啊,而後在發現時間不夠以後,瞎打了一發,無論了.開始幹T2,好像我須要在Trie上建2-sat,可是這彷佛只是50分作法啊,無論了,先碼,碼完以後,一路靜態查錯,發現錯誤好多啊,等我把靜態查錯完成,時間已經不太夠了,幸虧直接過了樣例.而後T3寫了一個輸出-1.
最後20+80+12=112分.
T1,我感受我要是當時早點從這深淵中脫離,去打一個狀壓,興許還能是一個O(n^2)dp,就能多拿一些分數,並且還會剩下不少時間,切題上癮的毛病最近又犯了,合理分配時間,儘可能拿高分纔是重中之重啊!
出此以外,想好了再打,在大多數狀況下都是一個比較好的選擇,沒想好去打,在某些狀況下也不是不能夠.
個人題解http://www.cnblogs.com/TSHugh/p/8625121.html
T2,個人作法因爲HZOI上沒有包而拿到了80分……然而,實際上,有兩種特殊狀況我沒有判,少拿了8分,仍是思惟不嚴謹……這道題的棧空間好像還有點迷……
清醒地去靜態查錯確實比無腦對拍,要高效得多,可是也有一些想不到的狀況,不用對拍是查不出來的.
個人題解http://www.cnblogs.com/TSHugh/p/8622572.html
T3,不管如何都不要放棄夢想……
個人題解http://www.cnblogs.com/TSHugh/p/8625433.html
math模擬賽
三道數學題……
平常先看一遍題,T1,多項式?仍是分三瓣的?T2,數位dp,矩陣乘法?T3,這題,好水,繼續往下翻,幹,模數不是質數……
先搞T3,前幾天作了一道poi的題,模數也不是質數,我用堆搞過去的,因而我開始嘗試用堆搞這道題,推了推,發現能夠,因而開始碼,碼完靜態查錯,過了因此樣例,寫暴力對拍,拍出了錯,結果是暴力寫錯了……開始懟T2,怒懟1h+以後,仍是隻會10分,感受是什麼dp,維護循環節的,不太會……因而趕忙棄坑去T1,先寫出30分暴力,而後想到了另外一個能夠和30分拼起來的暴力,迅速寫出,不過差了11s,沒有交上……
最後30+10+100=140分
T1,我那個差11s沒交上的暴力能夠多拿10分……
和我想我那個多10分的暴力同樣,50分作法的出發點也是第二個部分分裏m很小,50分作法用到了一個小性質,相同權值的點必定在一塊兒(證實很簡單:若是不是的話,必定能夠把相同權值的點移在一塊兒使答案變小),那麼咱們就能夠直接枚舉哪些權值出現了,而後把每一種權值的點看做一個點(正確性很好理解),這樣的話,再加上以前的30分dfs就能夠拿到50分了.
100分的話,繼續沿用50分的思路,把點分紅若干聯通塊.此次咱們確定不能像以前那樣枚舉權值,咱們此次以最高位上是否有1,把點分爲兩部分,這兩部分點必定內部聯通,之間有一條最小的邊(證實同50分作法).那麼咱們就會獲得一種狀態f,一種轉移,可是咱們發現咱們轉移的時候出現了一些奇奇怪怪的東西,g,求g的話,就不像f那樣好求了,咱們須要一些設計dp狀態與轉移的技巧,咱們能夠設計後綴意義的dp狀態,而且用後綴方案數累加獲得總和,這就有了g的轉移,以及p的出現,而p的出現,能夠沿用咱們一開始的思路進行轉移.這樣這道題就結束了,只是還有一些dp轉移的時候的一些小細節,以及,有一個必要的對於p的剪枝,具體細節見Kod.
這道題的話,在部分分的啓發性方面是很不錯的,還有這個dp套dp套dp的用法真是不用dfs打不出來額,還有,裏面dp的一些小細節還真是要命,必定要清楚咱們dp的時候會出現的細節狀況以及邊界條件,這樣纔有利於在dp的時候,不漏掉任何細節.
在常數方面的話,少模仍是挺厲害的,另外預處理一些東西也會帶來一些常數上的優化.可是,再怎麼常數優化,也不如好好地剪一下無用狀態,利用dfs裏的轉移的樣子推斷一下怎麼剪才強有力.因此說,dfs->剪枝這條有向邊不只有理有據還很很普適.
不斷地觀察性質,利用性質,強化性質,也是一種從0到100的途徑嘛.
數位上高位與低位之間的絕對優點,是一種不錯的解題思路.
從一開始的dp狀態與轉移,不斷地推出新的dp狀態與轉移,不失爲一種好的方法.
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=55; const int M=10; const int FL=260; const int P=258280327; inline int Pow(int x,int y){ int ret=1; while(y){ if(y&1)ret=(LL)ret*x%P; x=(LL)x*x%P,y>>=1; } return ret; } int f[N][M],g[N][N][M],h[N][N][M][FL],bin[N*N],c[N][N],cnm[N][N][N][N]; inline void Init(){ int i,j,k,l; bin[0]=1; for(i=1;i<N*N;++i) bin[i]=(bin[i-1]<<1)%P; c[0][0]=1; for(i=1;i<N;++i){ c[i][0]=1; for(j=1;j<=i;++j) c[i][j]=(c[i-1][j-1]+c[i-1][j])%P; } for(i=1;i<N;++i) for(j=0;j<=i;++j) for(l=1;l<N;++l) for(k=0;k<=l;++k) cnm[i][j][l][k]=(LL)c[i][j]*c[l][k]%P; memset(f,-1,sizeof(f)); memset(g,-1,sizeof(g)); memset(h,-1,sizeof(h)); for(i=0;i<M;++i) f[0][i]=f[1][i]=0; for(i=2;i<N;++i) f[i][0]=0; for(i=0;i<N;++i) for(j=0;j<N;++j){ g[i][j][0]=0; for(k=1;k<FL;++k) h[i][j][0][k]=0; } for(i=0;i<N;++i) for(j=0;j<M;++j) g[i][0][j]=g[0][i][j]=0; for(i=0;i<M;++i) for(j=0;j<FL;++j) if(bin[i]-1<j) for(l=0;l<N;++l) for(k=0;k<N;++k) h[l][k][i][j]=0; } inline int H(int s,int t,int m,int k){ if(h[s][t][m][k]!=-1)return h[s][t][m][k]; int i,j;LL ret=0; for(i=0;i<=s;++i){ for(j=0;j<=t;++j){ ret+= (i==0&&j==t)||(i==s&&j==0)? (bin[m-1]>=k?bin[(s+t)*(m-1)]:H(s,t,m-1,k-bin[m-1])): (LL)cnm[s][i][t][j]*((i!=0&&j!=0)?H(i,j,m-1,k):bin[(i+j)*(m-1)])%P*((i!=s&&j!=t)?H(s-i,t-j,m-1,k):bin[(s-i+t-j)*(m-1)]); } ret%=P; } return h[t][s][m][k]=h[s][t][m][k]=ret; } inline int G(int s,int t,int m){ if(g[s][t][m]!=-1)return g[s][t][m]; int i,ret=0,full=bin[m]-1; for(i=1;i<=full;++i) ret=(ret+H(s,t,m,i))%P; return g[t][s][m]=g[s][t][m]=ret; } inline int F(int n,int m){ if(f[n][m]!=-1)return f[n][m]; int i,ret=0; for(i=0;i<=n;++i) ret=(ret+(LL)c[n][i]*((LL)F(i,m-1)*bin[(m-1)*(n-i)]%P+(LL)F(n-i,m-1)*bin[(m-1)*i]%P+G(i,n-i,m-1)+(i==0||i==n?0:bin[(n+1)*(m-1)])))%P; return f[n][m]=ret; } int main(){ Init(); int n,m; scanf("%d%d",&n,&m); printf("%lld\n",(LL)F(n,m)*Pow(bin[n*m],P-2)%P); return 0; }
T2,我想得偏了,那個1e10的數據範圍,挺杜教篩的嘛.
首先咱們要發現一些東西,就是,題目中的數字個數就是沒有循環節的數的個數(循環同構)(能夠經過證實兩者的充要條件,來證實兩者的等價性),這個能夠反演,套路性地推一下就能夠A掉了.
可是還有一些騷騷的操做,好比說那個求i*10^i的前綴和的時候,我做爲一隻蒟,去模了一發std,而且以一種神奇的姿式理解了他.後來yzh、wzz紛紛爆出來用矩陣快速冪解決的方法,感受本身好蒟啊……後來zzh用等比數列直接手推出來那個式子……
我發現我觀察性質,發現問題實質,轉化模型,推式子的能力仍是好弱啊……高考數學沒學好啊……
解決問題的出發點很重要啊,出發點不對很危險啊,發現不對趕忙換啊.
忽然發現杜教篩是這樣的,感受本身以前理解的不是很好啊.
我以爲我識別矩陣乘法,識別反演的能力差得不行……
考試的時候必定要控制情緒啊,在這道題糾纏過久了……
#include <map> #include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=10000000; const int P=258280327; const int inv2=129140164; const int inv9=229582513; std::map<LL,int> gg; char mu[N+10]; int prime[N/10],len,g[N+10]; bool isnot[N+10]; int n; inline LL Pow(LL x,LL y){ LL ret=1; while(y){ if(y&1)ret=ret*x%P; x=x*x%P,y>>=1; } return ret; } inline int G(LL n){ if(n<=N)return g[n]; if(gg.count(n))return gg[n]; LL ret=0,i,last; for(i=2;i<=n;i=last+1){ last=n/(n/i); ret=(ret+((i+last)%P)*((last-i+1)%P)%P*inv2%P*G(n/i))%P; } ret=(1-ret)%P; return gg[n]=ret; } inline void Init(){ isnot[1]=true,mu[1]=1; register int i,j; for(i=2;i<=N;++i){ if(!isnot[i])prime[++len]=i,mu[i]=-1; for(j=1;prime[j]*i<=N;++j){ isnot[prime[j]*i]=true; if(i%prime[j]==0){ mu[prime[j]*i]=0; break; } mu[prime[j]*i]=-mu[i]; } } for(i=1;i<=N;++i) g[i]=(g[i-1]+i*mu[i])%P; } inline LL sum(LL n){ return (n%P*Pow(10,n)%P-(Pow(10,n)-1)*inv9%P)*inv9%P*10%P; } inline LL calc(LL n){ LL ret=0,i,last; for(i=1;i<=n;i=last+1){ last=n/(n/i); ret=(ret+(LL)(G(last)-G(i-1))*sum(n/i))%P; } return (ret+P)%P; } int main(){ Init(); LL n; scanf("%lld",&n); printf("%lld\n",calc(n)); return 0; }
T3,看錯了數據範圍……106當作10^6,不過給106這種數據範圍的,就是想讓人看錯的吧,之後注意一下,不過看到106的話,我更願意相信他打錯了.
實現代碼仍是不夠快,不夠準.
這道題用堆解決的方法的介紹:http://www.cnblogs.com/TSHugh/p/8568221.html
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> typedef long long LL; const int N=1000000; int prime[N+10],len,min[N+10],mini[N+10]; int mi[N*10],*begin[N+10],size[N+10]; bool isnot[N+10]; int n,P,x,y; struct Prime{ Prime *ch[2],*f; int key,id; inline void pushup(){ key=(LL)begin[id][size[id]]*ch[0]->key%P*ch[1]->key%P; } inline void* operator new(size_t); }*null,*root,node[N+10]; inline void build(Prime *&p,int id,Prime *fa){ if(id>len){ p=null; return; } p=node+id; p->f=fa; p->id=id; build(p->ch[0],id<<1,p); build(p->ch[1],(id<<1)|1,p); p->pushup(); } inline int quy(int x,int n){ register int ret=0,now=1; while((LL)now*x<=n)++ret,now*=x; return ret; } inline void Iput(int x){ while(min[x]) ++size[min[x]],x/=prime[min[x]]; } inline void update(Prime *p){ while(p!=null){ p->pushup(); p=p->f; } } inline void get(int x,int opt){ register int ti; while(min[x]){ ti=mini[x]; size[min[x]]+=opt*ti; update(node+min[x]); while(ti--) x/=prime[min[x]]; } } inline void Init1(){ isnot[1]=true; min[1]=0,mini[1]=0; register int i,j; for(i=2;i<=N;++i){ if(!isnot[i])prime[++len]=i,min[i]=len,mini[i]=1; for(j=1;prime[j]*i<=N;++j){ isnot[prime[j]*i]=true; min[prime[j]*i]=j; mini[prime[j]*i]=1; if(i%prime[j]==0){ mini[prime[j]*i]+=mini[i]; break; } } } null=node; null->ch[0]=null->ch[1]=null; null->f=null; null->id=0; null->key=1; } inline void Init2(){ scanf("%d%d%d%d",&n,&P,&x,&y); x=std::abs(x); y=std::abs(y); if(n-y-x<0){ puts("0"); exit(0); } if((x&1)!=((n+y)&1)){ puts("0"); exit(0); } register int i,j,pos=0,lim; for(i=1;i<=n;++i)Iput(i); for(i=1;i<=len;++i){ ++pos; begin[i]=mi+pos; mi[pos]=1; lim=size[i]+quy(prime[i],n)*2; for(j=1;j<=lim;++j) mi[pos+1]=(LL)mi[pos]*prime[i]%P,++pos; } build(root,1,null); } inline void Work(){ register int i,ans=0,a,b,c,d; a=0,b=x,c=(n-y-x)>>1,d=(n+y-x)>>1; for(i=1;i<=a;++i)get(i,-1); for(i=1;i<=b;++i)get(i,-1); for(i=1;i<=c;++i)get(i,-1); for(i=1;i<=d;++i)get(i,-1); ans=root->key; while(c&&d){ get(c,1),get(d,1); --c,--d; ++a,++b; get(a,-1),get(b,-1); ans=(ans+root->key)%P; } printf("%d\n",ans); } int main(){ //freopen("test.in","r",stdin); //freopen("need.out","w",stdout); //freopen("rio.in","r",stdin); Init1(); Init2(); Work(); return 0; }
這道題用crt解決的方法的介紹:http://www.cnblogs.com/TSHugh/p/8638050.html
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=1000010; int n,x,y,mod,P,p,phi,fac[N],num; inline int Pow(int x,int y){ int ret=1; while(y){ if(y&1)ret=(LL)ret*x%P; x=(LL)x*x%P,y>>=1; } return ret; } inline int cnt(int n){ return n?n/p+cnt(n/p):0; } inline int sum(int n){ return n?((LL)(n/P?Pow(fac[P],n/P):1)*fac[n%P]%P*sum(n/p)%P):1; } #define deal(n,a,b) int a=sum(n),b=cnt(n); inline int C(int n,int m){ if(m>n)return 0; deal(n,a0,b0) deal(m,a1,b1) deal(n-m,a2,b2) b0-=b1+b2; if(b0>=num)return 0; return (LL)a0*Pow(a1,phi-1)%P*Pow(a2,phi-1)%P*Pow(p,b0)%P; } inline int calc(){ int ret=0,i,a,b,c,d; fac[0]=1; for(i=1;i<=P&&i<=n;++i) fac[i]=(i%p)?(LL)fac[i-1]*i%P:fac[i-1]; a=0,b=x,c=(n-y-x)>>1,d=(n+y-x)>>1; while(c>=0){ ret=(ret+(LL)C(n,a+b)*C(a+b,a)%P*C(c+d,c)%P)%P; ++a,++b,--c,--d; } return ret; } int main(){ scanf("%d%d%d%d",&n,&mod,&x,&y); x=std::abs(x),y=std::abs(y); if(n-y-x<0||((x&1)!=((n+y)&1))){ puts("0"); return 0; } int i,s=mod,ans=0; for(i=2;s>1;++i){ if(i*i>s)i=s; if(s%i==0){ p=i,P=1,num=0; while(s%p==0) ++num,P*=p,s/=p; phi=P/p*(p-1); ans=(ans+(LL)calc()*(mod/P)%mod*Pow(mod/P,phi-1))%mod; } } printf("%d\n",ans); return 0; }
No.21省選模擬賽
開考先看題:T1,01分數規劃+網絡流?看數據範圍須要一些加邊的優化.T2,感受不是很難,可是感受比較奇怪.T3,啥啊.
先去看一看T1,有點困,無論了,先把暴力碼出來,碼出來以後,發現有些問題,而後迷迷糊糊地爲了正確性而增大了時間複雜度,等我清醒以後,意識到時間不太對,因而先去搞T2了.開始各類推解析幾何,各類高考數學……推出來+碼出來+調了調=2h+……可是,我忽然意識到一種極端狀況,並且這種極端狀況仍是在我手造簡單清真小樣例的時候見到的,我開始方了……我想去修改,可是時間已經很少了,並且我懼怕我還會想到更多更噁心的狀況,因而便棄掉他,去搞T3,T3打了一發10分暴力……
最後35+80+10=125分……
T1.啊……之後考試必定不要困了啊,雖然題目描述有誤,可是,我明明想到了正解,卻由於困,而選擇了錯誤的策略……
因此說,之後考試必定要把狀態調整好,什麼困啊,餓啊的,都不要發生.
這題的題幹就是有毒,描述錯了,若是描述沒問題,那就一點都不難.
原來4000點200000邊跑起網絡流來一點都不虛啊.
網絡流的加邊順序會成倍得影響程序運行效率,因此說,這玩意要是實在難分析,就本地試一下.
對於一些細節的分析,要快,要準.
題解裏,推式子推傻了,可是他那個加兩排Inf來去掉負權邊的影響的方法(能夠用兩種方法所得方案的轉化來證實其正確性)仍是值得學習的.
此外,ryf處理錯誤題意的方法,仍是比較好的,用check下一個數-1e-16的方法來檢查等於的狀況,比較吊.(可是真的不會渣精度嗎……不過有對拍,並且還客能夠拼程序,因此考試用這個點子仍是不虛的.)
#include <cstdio> #include <cstring> #include <algorithm> char xB[(1<<15)+10],*xS,*xT; #define gtc() (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) inline void read(int &x){ register char ch=gtc();bool ud=false; for(x=0;ch<'0'||ch>'9';ch=gtc())if(ch=='-')ud=true; for(;ch>='0'&&ch<='9';x=x*10+ch-'0',ch=gtc()); if(ud)x=-x; } const int N=4010; const int M=200010; const int oo=0x7fffffff; struct V{ int to,next,f; }c[M<<3]; int head[M+N],t; inline void add(int x,int y,int z){ c[++t].to=y,c[t].next=head[x],head[x]=t,c[t].f=z; c[++t].to=x,c[t].next=head[y],head[y]=t,c[t].f=0; } int n,m,e1[M],e2[M],w1[M],w2[N],S,T; int deep[M+N],queue[M<<1],front,back; #define poi(a) (a) #define edge(a) ((a)+n) inline void Init(){ read(n),read(m); int i; for(i=1;i<=n;++i) read(w2[i]); for(i=1;i<=m;++i) read(e1[i]),read(e2[i]),read(w1[i]); S=n+m+1,T=n+m+2; } inline bool bfs(){ memset(deep,-1,sizeof(deep)); front=back=0; queue[back++]=S; deep[S]=0; int x,i; while(front!=back){ x=queue[front++]; for(i=head[x];i;i=c[i].next) if(c[i].f&&deep[c[i].to]==-1){ deep[c[i].to]=deep[x]+1; if(c[i].to==T)return true; queue[back++]=c[i].to; } } return false; } inline int dfs(int x,int v){ if(x==T||v==0)return v; int ret=0,f; for(int i=head[x];i;i=c[i].next) if(c[i].f&&deep[c[i].to]==deep[x]+1){ f=dfs(c[i].to,std::min(v,c[i].f)); v-=f,ret+=f,c[i].f-=f,c[i^1].f+=f; if(!v)break; } if(!ret)deep[x]=-1; return ret; } inline bool check(int x){ int ret=0; t=1; int i; memset(head,0,sizeof(head)); for(i=1;i<=m;++i){ add(S,edge(i),w1[i]); add(edge(i),e1[i],oo); add(edge(i),e2[i],oo); ret+=w1[i]; } for(i=1;i<=n;++i) add(poi(i),T,w2[i]*x); while(bfs())ret-=dfs(S,oo); return ret>0; } inline void Print(){ int l=0,r=10000,mid,ans=0; while(l<=r){ mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; } printf("%d\n",ans); } int main(){ Init(),Print(); return 0; }
#include <cstdio> #include <cstring> #include <algorithm> char xB[(1<<15)+10],*xS,*xT; #define gtc() (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++) inline void read(int &x){ register char ch=gtc();bool ud=false; for(x=0;ch<'0'||ch>'9';ch=gtc())if(ch=='-')ud=true; for(;ch>='0'&&ch<='9';x=x*10+ch-'0',ch=gtc()); if(ud)x=-x; } const int N=4010; const int M=200010; const int oo=0x7fffffff; struct V{ int to,next,f; }c[(M+(N<<1))<<1]; int head[N],t; inline void add(int x,int y,int z1,int z2){ c[++t].to=y,c[t].next=head[x],head[x]=t,c[t].f=z1; c[++t].to=x,c[t].next=head[y],head[y]=t,c[t].f=z2; } int n,m,e1[M],e2[M],w1[M],w2[N],S,T,w3[N]; int deep[N],queue[N],front,back; inline void Init(){ read(n),read(m); int i; for(i=1;i<=n;++i){ read(w2[i]); w2[i]<<=1; } for(i=1;i<=m;++i){ read(e1[i]),read(e2[i]),read(w1[i]); w3[e1[i]]+=w1[i],w3[e2[i]]+=w1[i]; } S=n+1,T=n+2; } inline bool bfs(){ memset(deep,-1,sizeof(deep)); front=back=0; queue[back++]=S; deep[S]=0; int x,i; while(front!=back){ x=queue[front++]; for(i=head[x];i;i=c[i].next) if(c[i].f&&deep[c[i].to]==-1){ deep[c[i].to]=deep[x]+1; if(c[i].to==T)return true; queue[back++]=c[i].to; } } return false; } inline int dfs(int x,int v){ if(x==T||v==0)return v; int ret=0,f; for(int i=head[x];i;i=c[i].next) if(c[i].f&&deep[c[i].to]==deep[x]+1){ f=dfs(c[i].to,std::min(v,c[i].f)); v-=f,ret+=f,c[i].f-=f,c[i^1].f+=f; if(!v)break; } if(!ret)deep[x]=-1; return ret; } inline bool check(int x){ int ret=0,i; t=1,memset(head,0,sizeof(head)); for(i=1;i<=m;++i){ add(e1[i],e2[i],w1[i],w1[i]); ret+=w1[i]<<1; } for(i=1;i<=n;++i){ add(S,i,w2[i]*x,0); add(i,T,w3[i],0); } while(bfs())ret-=dfs(S,oo); return ret>0; } inline void Print(){ int l=0,r=5000,mid,ans=0; while(l<=r){ mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; } printf("%d\n",ans); } int main(){ //freopen("rio.txt","r",stdin); Init(),Print(); return 0; }
T2,我考試的代碼的正確性是有的,只不過有一個小細節,沒有判到(思惟仍是不嚴謹啊,同時考試的時候必定要確保本身的冷靜沉着),可是那對我炸掉的精度來講,已經可有可無了.
這道題充分證實了,在計算幾何中,向量法在精度方面的優秀,以及解析幾何法在精度方面的不足,並且在代碼複雜度方面,向量法仍是優於解析幾何法的,不過,一樣做爲計算幾何的基礎,兩者都是比較重要的吧.
解析幾何的話,就是三角函數什麼的一頓推.
向量的話,題解上雖說正解用的是向量法,可是沒說怎麼用,因此咱們用的是yzh的解方程法(仍是高考數學啊).大概就是利用幾個等式,好比向量的運算(加減法)、距離相等(到圓心距離等於半徑、對稱向量模長相等)等.
總之這題就是考察計算幾何基礎.
我考試的時候想到的極端狀況彷佛並無在數據裏出現……題解裏也沒有說……因此說,有些極端狀況,不要過於懼怕……
這種玄學計算幾何在考試的時候仍是不要耗太多精力,天知道有什麼鬼事發生,此次考試嚇得我給這題估0分……
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> const int N=55; const double oo=1e15; #define yy(a,b) (std::fabs((a)-(b))<1e-9) int n; struct Poi{ double x,y,z; inline void read(){ scanf("%lf%lf%lf",&x,&y,&z); } }; inline double disf(Poi a,Poi b){ return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z); } inline double dis(Poi a,Poi b){ return std::sqrt(disf(a,b)); } struct Line{ Poi s,t; inline void read(){ s.read(),t.read(); } }me; inline double dis(Line a,Poi b){ double x=dis(a.s,b); double y=dis(a.t,b); double z=dis(a.s,a.t); if(yy(x+y,z)||yy(x+z,y)||yy(y+z,x))return 0; double cos_k=(x*x+y*y-z*z)/(2*x*y); double sin_k=std::sqrt(1-cos_k*cos_k); double s=x*y*sin_k; return s/z; } struct Cir{ Poi h; double r; inline void read(){ h.read(),scanf("%lf",&r); } }cir[N]; inline double dis(Cir a,Line b){ double dis1=dis(b,a.h); if(dis1>a.r)return oo; double dis2=std::sqrt(a.r*a.r-dis1*dis1); double dis3=std::sqrt(disf(a.h,b.s)-dis1*dis1); double dis4=std::sqrt(disf(a.h,b.t)-dis1*dis1); double dis5=dis(b.s,b.t); if(yy(dis3+dis4,dis5))return dis3-dis2; if(dis3<dis4)return oo; return dis3-dis2; } inline Poi go(Line a,double b){ double dis1=dis(a.s,a.t); double k=b/dis1; Poi ret; ret.x=(a.t.x-a.s.x)*k+a.s.x; ret.y=(a.t.y-a.s.y)*k+a.s.y; ret.z=(a.t.z-a.s.z)*k+a.s.z; return ret; } inline Poi dc(Poi a,Poi b){ Poi ret; ret.x=2*b.x-a.x; ret.y=2*b.y-a.y; ret.z=2*b.z-a.z; return ret; } inline Poi dc(Poi a,Line b){ double dis1=dis(b,a); double dis2=dis(a,b.s); double dis3=dis(a,b.t); double dis4=std::sqrt(dis2*dis2-dis1*dis1); double dis5=std::sqrt(dis3*dis3-dis1*dis1); double dis6=dis(b.s,b.t); Poi mid=go(b,dis4*(yy(dis4+dis5,dis6)||dis3<dis2?1:-1)); return dc(a,mid); } inline Poi dc(Poi a,Poi b,Poi c){ return dc(a,(Line){b,c}); } inline Line Pia(Line a,Cir b){ Poi aha=go(a,dis(b,a)); Poi ehe=dc(a.s,aha,b.h); return (Line){aha,ehe}; } inline void Init(){ scanf("%d",&n); int i; for(i=1;i<=n;++i) cir[i].read(); me.read(); } inline int Pang(int last){ int i,id=-1; double min=oo,tmp; for(i=1;i<=n;++i){ if(i==last)continue; tmp=dis(cir[i],me); if(tmp<min){ min=tmp; id=i; } } return id; } inline void Work(){ int i,next=0; for(i=1;i<=11;++i){ next=Pang(next); if(next==-1)return; if(i==11){ printf("etc."); return; } printf("%d ",next); me=Pia(me,cir[next]); } } int main(){ //freopen("rio.txt","r",stdin); Init(),Work(); return 0; }
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> #define pf(a) ((a)*(a)) const int N=55; const double oo=1e15; int n; struct Poi{ double x,y,z; inline friend Poi operator -(Poi a,Poi b){ return (Poi){a.x-b.x,a.y-b.y,a.z-b.z}; } inline void read(){ scanf("%lf%lf%lf",&x,&y,&z); } }; struct Line{ Poi s,t; inline void read(){ s.read(),t.read(); t=t-s; } }me; struct Cir{ Poi h; double r; inline void read(){ h.read(),scanf("%lf",&r); } }cir[N]; inline double dis(Cir a,Line b){ double x=b.s.x-a.h.x; double y=b.s.y-a.h.y; double z=b.s.z-a.h.z; double A=pf(b.t.x)+pf(b.t.y)+pf(b.t.z); double B=2*b.t.x*x+2*b.t.y*y+2*b.t.z*z; double C=x*x+y*y+z*z-a.r*a.r; double det=B*B-4*A*C; if(det<0)return oo; double k=(-B-std::sqrt(det))/(2*A); if(k<0)return oo; return std::sqrt(pf(k*b.t.x)+pf(k*b.t.y)+pf(k*b.t.z)); } inline Poi go(Line a,double b){ double k=b/std::sqrt(pf(a.t.x)+pf(a.t.y)+pf(a.t.z)); Poi ret; ret.x=a.t.x*k+a.s.x; ret.y=a.t.y*k+a.s.y; ret.z=a.t.z*k+a.s.z; return ret; } inline Line Pia(Line a,Cir b){ Poi aha=go(a,dis(b,a)); Poi o=a.s-aha; Poi m=aha-b.h; Poi t; double k=(2*m.x*o.x+2*m.y*o.y+2*m.z*o.z)/(m.x*m.x+m.y*m.y+m.z*m.z); t.x=k*m.x-o.x; t.y=k*m.y-o.y; t.z=k*m.z-o.z; return (Line){aha,t}; } inline void Init(){ scanf("%d",&n); int i; for(i=1;i<=n;++i) cir[i].read(); me.read(); } inline int Pang(int last){ int i,id=-1; double min=oo,tmp; for(i=1;i<=n;++i){ if(i==last)continue; tmp=dis(cir[i],me); if(tmp<min){ min=tmp; id=i; } } return id; } inline void Work(){ int i,next=0; for(i=1;i<=11;++i){ next=Pang(next); if(next==-1)return; if(i==11){ printf("etc."); return; } printf("%d ",next); me=Pia(me,cir[next]); } } int main(){ Init(),Work(); return 0; }
T3,考試的時候給這題時間少了,還有就是個人數學能力不強.
咱們先看怎麼是最大的.那就是n!分解質因數以後,把奇數size的質因數size-1後就是了.爲何,由於每一個質因數的size同時達到了最大.
若是按照分解質因數的思路走下去的話,應該會想到的吧?(an,wei,還不是不會觀察性質)
那麼咱們的方案數就是,把那些奇數size質因數分堆成數的方案數*2(*2是由於1選不選均可以).
那麼咱們通過小程序發現(小程序大用處,小程序意識++,並且判斷解題出發點也是比較重要的),這些數不多啊,因此能dfs出許多數來,然而只能得40分,看來這數據造的很良(xin)心(ji)啊.
題解的40分作法,是狀壓dp啊,不過這玩意也就是用來過渡到100分的(不過度集合,就已經與狀壓有必定聯繫了).
100分的話,就是40分狀壓加按根號分類處理.
根號分類處理彷佛在質因數這裏用得比較多,並且還常常和狀壓dp混在一塊兒.固然這玩意還有其餘的用處,好比PA某題……
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=3000; int prime[N+10],min[N+10],len,size[N+10]; bool isnot[N+10]; int n,lit[N+10],big[N+10],lm,bm,now,last,could[(1<<13)+10]; LL ans,f[2][(1<<13)+10]; inline void Iput(int x){ while(min[x]) ++size[min[x]],x/=prime[min[x]]; } inline void Init(){ isnot[1]=true; min[1]=0; register int i,j; for(i=2;i<=N;++i){ if(!isnot[i])prime[++len]=i,min[i]=len; for(j=1;prime[j]*i<=N;++j){ isnot[prime[j]*i]=true; min[prime[j]*i]=j; if(i%prime[j]==0)break; } } } inline void dp1(){ now=0,last=1; f[0][0]=1; int i,j,k,full,sum; full=(1<<lm)-1; for(i=0;i<=full;++i){ sum=1; for(j=0;j<lm;++j) if(i&(1<<j)){ sum*=lit[j+1]; if(sum>n){sum=-1;break;} } could[i]=sum; } for(i=1;i<=lm;++i){ full=(1<<(i-1))-1; now^=1,last^=1; memcpy(f[now],f[last],sizeof(f[now])); for(j=0;j<=full;++j){ if(could[j|(1<<i-1)]!=-1) for(k=full^j;;k=(k-1)&(full^j)){ f[now][(j|(1<<i-1))|k]+=f[last][k]; if(!k)break; } } } } inline void dp2(){ int i,j,k,full=(1<<lm)-1; for(i=1;i<=bm;++i){ now^=1,last^=1; memset(f[now],0,sizeof(f[now])); for(j=0;j<=full;++j){ if(could[j]!=-1&&could[j]*big[i]<=n) for(k=full^j;;k=(k-1)&(full^j)){ f[now][j|k]+=f[last][k]; if(!k)break; } } } ans=f[now][full]; } inline void Work(){ scanf("%d",&n); int i,st=std::sqrt(n+0.5); for(i=1;i<=n;++i) Iput(i); for(i=1;i<=len;++i){ if((size[i]&1)==0||size[i]==1)continue; if(prime[i]<=st)lit[++lm]=prime[i]; else big[++bm]=prime[i]; } dp1(),dp2(); printf("%lld\n",ans<<1); } int main(){ Init(),Work(); return 0; }
No.22省選模擬賽
相對來講,此次的題仍是比較簡單吧.
原本是先看一遍題,不過此次一邊看一邊打了.
T1,題幹簡潔,應該是個反演,先把暴力碼出來.碼完去看T2,題幹簡潔,不是很會,先碼出來一個12分暴力.而後去看T3,感受從哪裏見過,無論了,先碼個暴力,不對啊,個人複雜度不對,又想了想,想出來一個小性質,而後開始碼,碼着碼着忽然意識到我tm打的是正解啊.打完T3,沒調,又回到了T1,推了一下,推出來一個O(n^2ln(n^2))的作法,碼了出來,測了一下極限數據,4s,完蛋.我先拿這玩意和暴力拍了一下,而後卡了卡常,卡到了2.1s,靜態查了一波錯,一看時間不對,感受在這裏查錯時間太長了,並且過低效了.因而去調T3,我幹,這玩意咋打暴力啊,造數據麻煩也就算了,tmd不會打暴力啊,YY了一會,不會,因而趕忙靜態查錯一波,去看T2了.T2,在原來12分的dfs基礎之上,打了一個32分的狀壓.考試時間很少了,因而去手造小樣例測試T3程序.
最後100+32+100=232分……
T1,個人作法正好卡過……正解是O(n^2lnn)的,寫醜了和個人作法差很少……不過着這玩意出5000還真是有點卡……
考試查錯的時候,有點困,因此查錯過於慢,效率也過於低了.
個人式子和他的式子基本同樣,只不過寫法不同,因此致使,解法不同.
O(n^2)預處理gcd用得還挺多,並且對於特殊模數利用天然溢出是能夠卡常的.
一維一維得推廣,不僅是在遞推和組合數學的時候用到,反演也是能夠的.
T2,我只是想到了壓每一位,可是沒想到去換個角度搞,可是我感受我當時確實有點不冷靜,也有點困,還有點「躊躇滿志」……
因此說以上說的這些,不能每次光說不改,立刻就要省選了……
這道題的思路具體就是解決不合法方案,正解的方法是相似容斥的方法,看到以後,發現十分清真美麗.
還有一種方法,能夠「強制合法」.zzh就是這種方法.這種方法穩72,可是對於這道題的數據能夠卡到92.思路就是統計同一類狀態的可能結束點數,這樣就能夠計算了.
這題的dp仍是比較妙的啊.
T3,是一道十分好想且好寫的半平面交,只不過到如今我仍是很不會寫暴力.
因此說不會寫暴力的話,就趕快去手玩小樣例吧.
zzh彷佛被50分暴力套住,覺得本身想到的是50分暴力,因此就把100分打成了50分.一開始我也覺得本身的正解是50分暴力,後來打着打着才發現本身是正解,因此分析複雜度仍是很重要的吧.
(這個是最後一篇,當時還沒寫完……寫的時候在進行高密度考試因此和以前寫得不同……)
如今,咱們的「肚子」是無限大的,然而「吃飯」是須要時間的.因此說,既然如今「食物」多了,那咱們挑着「好吃的」去「吃」.
思考與總結,寫下來是次要的,進腦子才重要.
avoid:
睏倦、慌亂、自滿、猶豫不決、興奮;
考試以外的事情帶到考試上來;
爲沒必要要的小事去找不值得的氣;
盲目去冒一些沒必要要的險,好比在不肯定的狀況下使用不熟悉的知識點;
一樣的錯誤重複出現;
exp:
路子不通,換路子;
不要放棄任何一道題,再看兩眼就會有新的但願;
猶豫不決會把小的問題變大,會把小的疑惑放大,因此說遇事果決是不錯的選擇;
保持鎮定,時刻冷靜,是拿高分的基礎;
想要翻盤,就不要想着本身在翻盤,要想着本身正在打一場新的比賽(這句話不僅是說一試,一樣也在說在一場考試中的先後期);
別人的節奏是別人的節奏,本身的路子是本身的路子,就算別人AK,跟我不要緊,就算別人爆零,跟我也不要緊;
多一些思考,多一線但願;
時間分配的均衡度與得分每每是正相關的;
碼,要思路清晰,速戰速決;
調,要保持冷靜,速戰速決;
想打對拍就不要拖延;
想驗證程序,可是不會寫暴力,或者不會造數據,就快手造小樣例,而且加大眼動(腦動)查錯力度;
沒時間優化算法,能夠,可是懶得去優化是絕對不能夠的,尤爲是你明知道你的程序慢,還不去優化;
不要以爲正解的時間複雜度必定不卡時限;
正確分析複雜度有時候會讓你避免沒必要要的錯誤;
knowledge:
剪枝是dfs的重中之重;
最小割的Inf邊,是網絡流的巧妙;
No.22省選模擬賽:
O(n^2)預處理gcd能夠用來減小複雜度;
維數遞增地推廣,不只僅是遞推能夠用,一些數學規律,數學公式也能夠嘗試這樣作;
遇到特殊模數,有時候能夠用天然溢出卡常;
分析問題,轉化問題,會方便你設計solution;
相似差分又相似容斥地減去無用狀態,會方便dp;
積累反演式子:
待續……