Diffie-Hellman 密鑰交換協議是一種簡單有效的密鑰交換方法。它可讓通信雙方在沒有事先約定密鑰(密碼)的狀況下,經過不安全的信道(可能被竊聽)創建一個安全的密鑰 \(K\),用於加密以後的通信內容。ios
假定通信雙方名爲 Alice 和 Bob,協議的工做過程描述以下(其中 \(\bmod\) 表示取模運算):編程
可見,這個過程當中可能被竊聽的只有 \(A,B\),而 \(a,b,K\) 是保密的。而且根據 \(A,B,P,g\) 這 \(4\) 個數,不能輕易計算出 \(K\),所以 \(K\) 能夠做爲一個安全的密鑰。安全
固然安全是相對的,該協議的安全性取決於數值的大小,一般 \(a,b,P\) 都選取數百位以上的大整數以免被破解。然而若是 Alice 和 Bob 編程時偷懶,爲了不實現大數運算,選擇的數值都小於 \(2^{31}\),那麼破解他們的密鑰就比較容易了。網絡
第一行包含兩個空格分開的正整數 \(g\) 和 \(P\)。加密
第二行爲一個正整數 \(n\),表示 Alice 和 Bob 共進行了 \(n\) 次鏈接(即運行了 \(n\) 次協議)。spa
接下來 \(n\) 行,每行包含兩個空格分開的正整數 \(A\) 和 \(B\),表示某次鏈接中,被竊聽的 \(A,B\) 數值。code
BSGS裸題。blog
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> using namespace std; const int Mo=100003; int a,b,A,B,P,n; struct HashTable { struct Line{int next,v1,v2;}a[Mo]; int head[Mo],cnt; void reset() {memset(head,0,sizeof(head));cnt=0;} void Add(int p,int v1,int v2) {a[++cnt]=(Line){head[p],v1,v2};head[p]=cnt;} int Query(int x) { for(int i=head[x%Mo];i;i=a[i].next) if(a[i].v1==x) return a[i].v2;return -1; } }Hash; int ksm(int x,int k) { int s=1;for(;k;k>>=1,x=1ll*x*x%P) if(k&1) s=1ll*s*x%P;return s; } int BSGS(int A,int B,int P) { int M=sqrt(P)+1;Hash.reset(); for(int i=0,t=B;i<M;i++,t=1ll*t*A%P) Hash.Add(t%Mo,t,i); for(int i=1,bs=ksm(A,M),t=bs;i<=M;i++,t=1ll*t*bs%P) if(Hash.Query(t)!=-1) return i*M-Hash.Query(t);return -1; } int main() { cin>>A>>P>>n; while(n--) { cin>>B>>b;a=BSGS(A,B,P); printf("%d\n",ksm(b,a)); } return 0; }
當今社會,在社交網絡上看朋友的消息已經成爲許多人生活的一部分。一般,一個用戶在社交網絡上發佈一條消息(例如微博、狀態、Tweet 等)後,他的好友們也能夠看見這條消息,並可能轉發。轉發的消息還能夠繼續被人轉發,進而擴散到整個社交網絡中。遊戲
在一個實驗性的小規模社交網絡中咱們發現,有時一條熱門消息最終會被全部人轉發。爲了研究這一現象發生的過程,咱們但願計算一條消息全部可能的轉發途徑有多少種。爲了編程方便,咱們將初始消息發送者編號爲 \(1\),其餘用戶編號依次遞增。ip
該社交網絡上的全部好友關係是已知的,也就是說對於 A、B 兩個用戶,咱們知道 A 用戶能夠看到 B 用戶發送的消息。注意可能存在單向的好友關係,即 A 能看到 B 的消息,但 B 不能看到 A 的消息。
還有一個假設是,若是某用戶看到他的多個好友轉發了同一條消息,他只會選擇從其中一個轉發,最多轉發一次消息。從不一樣好友的轉發,被視爲不一樣的狀況。
若是用箭頭表示好友關係,下圖展現了某個社交網絡中消息轉發的全部可能狀況。(初始消息是用戶 \(1\) 發送的,加粗箭頭表示一次消息轉發)
對於 \(30\%\) 的數據,\(1≤n≤10\)。
對於 \(100\%\) 的數據,\(1\leq n\leq 250, 1\leq a_i,b_i\leq n, 1\leq m\leq n(n-1)\)。
矩陣樹定理裸題。
對於有向圖有根樹的計數,邊\(x->y\)則矩陣\(a[x][y]--,a[y][y]++\),即度數矩陣爲入讀。同時注意刪去根所在的那一行一列。
#include<iostream> using namespace std; const int P=10007; int n,m,g[251][251],ans=1; int main() { cin>>n>>m; for(int i=1,a,b;i<=m;i++) { scanf("%d%d",&b,&a); g[a][b]--;g[b][b]++; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) (g[i][j]+=P)%=P; for(int i=2;i<=n;i++) for(int j=i+1;j<=n;j++) while(g[j][i]) { int t=g[i][i]/g[j][i];ans=-ans; for(int k=i;k<=n;k++) g[i][k]=(P+g[i][k]-1ll*t*g[j][k]%P)%P,swap(g[i][k],g[j][k]); } for(int i=2;i<=n;i++) ans=1ll*ans*g[i][i]%P; cout<<(ans+P)%P<<endl; }
咱們稱一個僅由 \(0,1\) 構成的序列爲「交錯序列」,當且僅當序列中沒有相鄰的 \(1\)(能夠有相鄰的 \(0\))。例如,\(000,001,101\) 都是交錯序列,而 \(110\) 則不是。
對於一個長度爲 \(n\) 的交錯序列,統計其中 \(0\) 和 \(1\) 出現的次數,分別記爲 \(x\) 和 \(y\)。給定參數 \(a,b\),定義一個交錯序列的特徵值爲 \(x^ay^b\)。注意這裏規定任何整數的 \(0\) 次冪都等於 \(1\)(包括 \(0^0=1\))。
顯然長度爲 \(n\) 的交錯序列可能有多個。咱們想要知道,全部長度爲 \(n\) 的交錯序列的特徵值的和,除以 \(m\) 的餘數。(\(m\) 是一個給定的質數)
例如,所有長度爲 \(3\) 的交錯串爲:\(000,001,010,100,101\)。當 \(a=1,b=2\) 時,可計算:\(3^1\times0^2+2^1\times1^2+2^1\times1^2+2^1\times1^2+1^1\times2^2=10\)。
對於 \(30\%\) 的數據,\(1\leq n\leq 15\)。
對於 \(100\%\) 的數據,\(1\leq n\leq 10^7,0\leq a,b\leq 45, m<10^8\)。
惟一一道有點意思的題目。
首先考慮\(\mathcal O(nlog)\)暴力:枚舉\(1\)有多少個,此時0的方案數能夠直接用組合數算:\(C(n+1-i,i)\)。有\(i\)個1,那麼有\(n-i\)個0,咱們能夠在這\(n-i\)個位置以及第0個位置、一共\(n+1-i\)個位置中,選擇\(i\)個位置在後面放1。
因此答案是\(\sum_{i=0}^{\lfloor \frac{n+1}{2}\rfloor} C_{n+i-1}^{i}(n-i)^ai^b\)。
然而這隻有暴力的三十分,因爲數據範圍極具誤導性,卡了半個小時常仍然無果(甚至試了線性篩快速冪)。
正解是這樣的:用二項式定理拆開式子
\[x^ay^b=(n-y)^ay^b=\sum_{i=0}^aC_a^in^i(-y)^{a-i}y^b=\sum_{i=0}^a(-1)^{a-i}C_a^in^iy^{a+b-i}\]
如今考慮的就是\(y\)的全部方案的冪次和是什麼,或者根據剛纔的式子,能夠獲得
\[Ans=\sum_{y=0}^{\lfloor \frac{n+1}{2}\rfloor}C_{n+y-1}^y(n-y)^ay^b=\sum_{i=0}^{a}(-1)^{a-i}C_a^in^i\sum_{y=0}^{\lfloor \frac{n+1}{2}\rfloor}y^{a+b-i}C_{n+1-y}^y\]
如今咱們要求算\(f[k]\)表示後面那個\(\sum\)中\(a+b-i=k\)時候的和。
令\(f[k][i][0/1]\)表示長度爲\(k\)的序列,後面那一坨指數是\(i\)的和。
有轉移:
\[f[k][i][0]=f[k-1][i][0]+f[k-1][i][1]\]
表示在末尾接一個0,能夠從1和0同時轉移。
\[f[k][i][1]=\sum_{j=0}^i C_i^jf[k-1][i][0]\]
表示在末尾接一個1,至關於給\(y\)+1,二項式展開後是這幅面孔。
而後因爲k比較大,因此經過矩陣轉移,這種多維DP的矩陣會分區,好比這題的矩陣以下構造:
#include<iostream> #include<cstring> using namespace std; const int N=1e7+10; int n,a,b,p,C[100][100],l,Ans; struct matrix { long long a[200][200]; matrix () {memset(a,0,sizeof(a));} matrix operator * (matrix A) { matrix C; for(int i=1;i<=l;i++) for(int j=1;j<=l;j++) { for(int k=1;k<=l;k++) C.a[i][j]+=a[i][k]*A.a[k][j]; C.a[i][j]%=p; } return C; } }A,B; int main() { cin>>n>>a>>b>>p; for(int i=0;i<=99;i++) C[i][0]=1; for(int i=1;i<=99;i++) for(int j=1;j<=99;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%p; l=(a+b+1)*2; for(int i=1;i<=l/2;i++) { A.a[i][i]=A.a[i][l/2+i]=1; for(int j=1;j<=i;j++) A.a[l/2+i][j]=C[i-1][j-1]; } for(int i=1;i<=l;i++) B.a[i][i]=1; for(int k=n;k;k>>=1,A=A*A) if(k&1) B=B*A; for(int i=0,pw=1;i<=a;i++,pw=1ll*pw*n%p) (Ans+=1ll*(((a-i)&1)?-1:1)*C[a][i]*pw%p*(B.a[a+b-i+1][1]+B.a[l/2+a+b-i+1][1])%p)%=p; cout<<(Ans+p)%p; }
使用過 Android 手機的同窗必定對手勢解鎖屏幕不陌生。 Android 的解鎖屏幕由 \(3 \times 3\) 個點組成,手指在屏幕上畫一條線,將其中一些點鏈接起來,便可構成一個解鎖圖案。以下面三個例子所示:
畫線時還須要遵循一些規則:
對於最後一條規則,參見下圖的解釋。左邊兩幅圖違反了該規則;而右邊兩幅圖(分別爲 $ 2 \rightarrow 4 \rightarrow 1 \rightarrow 3 \rightarrow 6$ 和 $ 6 \rightarrow 5 \rightarrow 4 \rightarrow 1 \rightarrow 9 \rightarrow 2$ )則沒有違反規則,由於在「跨過」點時,點已經被使用過了。
如今工程師但願改進解鎖屏幕,增減點的數目,並移動點的位置,再也不是一個九宮格形狀,但保持上述畫線規則不變。
請計算新的解鎖屏幕上,一共有多少知足規則的畫線方案。
對於 \(30\%\) 的數據, \(1 \le n \le 10\) 。
對於 \(100\%\) 的數據, \(-1000 \le x_i ,y_i \le 1000\) , $ 1 \le n < 20$ 。各點座標不相同。
狀壓DP。
#include<iostream> #include<cmath> using namespace std; const int mod=1e8+7; int n,x[30],y[30],bit[30],S[30][30]; int dp[1<<20][21],ans; double dis(int i,int j) {return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));} int main() { cin>>n;bit[0]=1; for(int i=1;i<=n;i++) cin>>x[i]>>y[i]; for(int i=1;i<=21;i++) bit[i]=1<<(i-1); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) if(fabs(dis(i,j)-dis(i,k)-dis(j,k))<1e-8&&k!=i&&k!=j) S[i][j]|=bit[k]; for(int i=1;i<=n;i++) dp[bit[i]][i]=1; for(int zt=0;zt<bit[n+1];zt++) for(int i=1;i<=n;i++) if(dp[zt][i]) for(int j=1;j<=n;j++) { if(bit[j]&zt) continue; if((S[i][j]&zt)!=S[i][j]) continue; (dp[zt|bit[j]][j]+=dp[zt][i])%=mod; } for(int zt=0,res=0;zt<bit[n+1];res=0,zt++) { for(int i=1;i<=n;i++) if(zt&bit[i]) res++; if(res<=3) continue; for(int i=1;i<=n;i++) (ans+=dp[zt][i])%=mod; } cout<<ans<<endl; }
九連環是一種源於中國的傳統智力遊戲。如圖所示,九個圓環套在一把「劍」上,而且互相牽連。遊戲的目標是把九個圓環從「劍」上卸下。
圓環的裝卸須要遵照兩個規則。
與魔方的變幻無窮不一樣,解九連環的最優策略是惟一的。爲簡單起見,咱們以「四連環」爲例,演示這一過程。這裏用 1
表示環在「劍」上, 0
表示環已經卸下。
初始狀態爲 1111
,每部的操做以下:
1101
(根據規則 \(2\) ,卸下第 \(2\) 個環)1100
(根據規則 \(1\) ,卸下第 \(1\) 個環)0100
(根據規則 \(2\) ,卸下第 \(4\) 個環)0101
(根據規則 \(1\) ,裝上第 \(1\) 個環)0111
(根據規則 \(2\) ,裝上第 \(2\) 個環)0110
(根據規則 \(1\) ,卸下第 \(1\) 個環)0010
(根據規則 \(2\) ,卸下第 \(3\) 個環)0011
(根據規則 \(1\) ,裝上第 \(1\) 個環)0001
(根據規則 \(2\) ,卸下第 \(2\) 個環)0000
(根據規則 \(1\) ,卸下第 \(1\) 個環)因而可知,卸下「四連環」至少須要 \(10\) 步。隨着環數增長,須要的步數也會隨之增多。例如卸下九連環,就至少須要 \(341\) 步。
請你計算,有 \(n\) 個環的狀況下,按照規則,所有卸下至少須要多少步。
對於 \(10\%\) 的數據, \(1 \le n \le 10\) 。
對於 \(30\%\) 的數據, \(1 \le n \le 30\) 。
對於 \(100\%\) 的數據, \(1 \le n \le 10^5 , 1 \le m \le 10\) 。
經過這\(f[4]\)的演示,能夠發現:\(f[4]=f[2]+1+f[2]+f[3]\),因此獲得遞推:\(f[i]=2f[i-2]+f[i]+1\)
因此經過觀察可得,\(f[2k+1]=\frac{4^{k+1}-1}{3},f[2k]=\frac{f[2k+1]-1}{2}\)
因而把\(4\)的1到16次方高精度預處理出來,而後直接上高精度乘法除法就行了。
不知道爲何要FFT
#include<iostream> #include<cstring> using namespace std; int m,n; struct Bignum { int a[100000],l; Bignum () {memset(a,0,sizeof(a));l=0;} void del() { a[1]--; for(int i=1;i<l;i++) if(a[i]<0) a[i]=9,a[i+1]--; } void div(int x) { for(int i=l,res=0;i>=1;i--) res+=a[i],a[i]=res/x,res%=x,res*=10; while(!a[l]) l--; } Bignum operator * (Bignum A) { Bignum C;C.l=A.l+l-1; for(int i=1;i<=l;i++) for(int j=1;j<=A.l;j++) C.a[i+j-1]+=a[i]*A.a[j]; for(int i=1;i<C.l;i++) C.a[i+1]+=C.a[i]/10,C.a[i]%=10; while(C.a[C.l]>9) C.a[C.l+1]+=C.a[C.l]/10,C.a[C.l]%=10,C.l++; while(!C.a[C.l]) C.l--; return C; } void print() {for(int i=l;i>=1;i--) printf("%d",a[i]);puts("");} }bs[17],Ans; int main() { bs[0].l=1;bs[0].a[1]=4; for(int i=1;i<=16;i++) bs[i]=bs[i-1]*bs[i-1]; for(cin>>m;m;m--) { cin>>n;Ans.l=Ans.a[1]=1; int k=(n-(n&1))/2+1; for(int i=0;i<=16;i++) if(k&(1<<i)) Ans=Ans*bs[i]; Ans.del(),Ans.div(3); if(!(n&1)) Ans.del(),Ans.div(2); Ans.print(); } return 0; }
已知一個長度爲 \(n\) 的整數數列 \(a_1,a_2,\dots,a_n\) ,給定查詢參數 \(l\) 、 \(r\) ,問在 \(a_l,a_{l+1},\dots,a_r\) 區間內,有多少子序列知足異或和等於 \(k\) 。也就是說,對於全部的 $ x,y ( l \le x \le y \le r)$ ,知足 $ a_x \oplus a_{x+1} \oplus \dots \oplus a_y=k $ 的 \(x,y\) 有多少組。
對於 \(30\%\) 的數據, \(1 \le n,m \le 1000\) 。
對於 \(100\%\) 的數據, \(1 \le n,m \le 10^5 , 0 \le k,a_i \le 10^5 , 1 \le l_j \le r_j \le n\) 。
處理好異或前綴和後,也就是區間\(l,r\)內每一對\(s[i]=x,s[j]=x\oplus k\)都會對答案產生1的貢獻。
用桶而後莫隊就行了。
#include<iostream> #include<algorithm> #include<cmath> #define ll long long using namespace std; const int N=5e5; int n,m,k,a[N],s[N],bel[N],tong[N],blk; ll Ans; struct Que{int l,r,id;ll ans;}Q[N]; int cmp (const Que&A,const Que&B) {return bel[A.l]==bel[B.l]?A.r<B.r:bel[A.l]<bel[B.l];} int cmp1(const Que&A,const Que&B) {return A.id<B.id;} void Add(int p) {Ans+=tong[s[p]^k];tong[s[p]]++;} void Del(int p) {Ans-=tong[s[p]^k];tong[s[p]]--;} int main() { cin>>n>>m>>k; for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]^a[i]; for(int i=1;i<=m;i++) scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].id=i,Q[i].l--; blk=sqrt(n); for(int i=0,nw=0;i<=n;bel[i]=nw,i++) if(i%blk==0) nw++; sort(Q+1,Q+m+1,cmp); for(int i=1,l=1,r=0;i<=m;i++) { while(r<Q[i].r) Add(++r); while(l>Q[i].l) Add(--l); while(r>Q[i].r) Del(r--); while(l<Q[i].l) Del(l++); Q[i].ans=Ans; } sort(Q+1,Q+m+1,cmp1); for(int i=1;i<=m;i++) printf("%lld\n",Q[i].ans); return 0; }
CQOI搞什麼啊題目質量一點都不行啊。
5道模板真的使人無發可說。。
自測起來除了D1T3大概是50分外,其餘都秒了。
可是考場不開O2的話高精度可能會被卡。
總之最多550也是十分危險的,由於若是Noip是發揮良好的話是520的隊線,可是我Noip炸了啊!
願HNOI不會這樣。。