1. 程式人生 > >省選模擬賽記錄

省選模擬賽記錄

href one 正常 有變 小點點 lose prim click 觀察

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好神啊……


雀巢咖啡系列模擬賽 XLI
異化多肽(T1):
看到這個東西還有那個模數就像要去ntt一發,然後開始推式子.
推啊推,推啊推,推出來一個等比數列求和的式子,很開心,感覺自己要A題了.
然後快速碼出快速冪ntt+多項式求逆,調完之後,極限數據5s!!!感覺藥丸,然後一波卡常,3s左右,就沒再管……
最後90,T了一個點,發現一個很蠢蛋的問題——我湊,我快速冪ntt個蛋啊,mdzz,我要快速冪ntt求的那個多項式在mod x^(n+1)意義下是0啊!!!!感覺自己蠢到家了……
所以這題只不過是一個比較裸的多項式求逆,我為什麽要快速冪ntt呢?應該就是我多項式這兒太菜了,當然我覺得也有考試的時候腦子不太正常的原因,還有我的極限思想應該是不太好.

推出這道題的多項式求逆還可以直接從生成函數的實際意義和遞推式的角度.

技術分享圖片
#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;
}
View Code

編碼病毒(T2):
讀懂題之後,哇塞,時限有4s,完全可以O(n^2)再來一個1/32的常數用bitset水之啊,然後15min搞好……
最後100,發現正解是比bitset還要水的水fft,就是一個變了一下形的bzoj2194,但是bitset比這玩意好寫到不知道哪裏去了……

技術分享圖片
#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;
}
Bitset 技術分享圖片
#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;
}
fft

數論函數簇(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那個折半大異小同.


省選模擬賽記錄