題目鏈接:https://ac.nowcoder.com/acm/contest/3002#question
emmm,沒什么好說的,就是送人頭了,
題目說明:
A.honoka和格點三角形 B.kotori和bangdream C.umi和弓道 D.hanayo和米飯
(計數問題) (水題計算) (卡精度題) (水題)
E.rin和快速迭代 F.maki和tree G.eli和字符串 H.nozomi和字符串
(暴力) (DFS遍歷) (二分+前綴和) (二分+前綴和)
I.nico和niconiconi J.u's的影響力
(DP) (矩陣快速冪+數論)
A.honoka和格點三角形
題目大意:給你n*m的點圖,問你其中有多少個好三角形,其中好三角形定義如下:
1.所有的點在格點上
2.至少一條邊平行於x或y軸
3.其面積為1
樣例:
輸入
2 3
輸出
6
輸入
100 100
輸出
7683984
沒什么好說的,上圖:


以下是AC代碼:
#include <bits/stdc++.h> using namespace std; #define ll long long const int mac=2e5+10; const int inf=1e9+10; const int mod=1e9+7; int main() { ll n,m; cin>>n>>m; ll a=((n-2)*n%mod*(m-1)%mod+(m-2)*m%mod*(n-1)%mod)%mod*2%mod; ll b=((m-2)*(n-1)%mod*n+(n-2)*(m-1)%mod*m)%mod*2%mod; ll c=((n-2)*(m-1)%mod+(m-2)*(n-1)%mod)%mod*4%mod; cout<<(a+b-c+mod)%mod<<endl; return 0; }
B.kotori和bangdream
題目大意:你有x%的概率敲出perfect的響聲,得分為a,其余的得b分,問你n個字符你能拿多少分
輸入
100 50 500 400
輸出
45000.00
每個音符的得分期望是$x%*a+(100-x)%*b$,n個音符的得分總期望就乘以n好了
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; int main() { int n,x,a,b; scanf ("%d%d%d%d",&n,&x,&a,&b); double pa,pb; pa=x*1.0/100;pb=(100-x)*1.0/100; double ans=n*pa*a+n*pb*b; printf("%.2f\n",ans); return 0; }
C.umi和弓道
題目大意:給你一個坐標,你要射n個點,要使得你最多只能射到k個,求擋板的最小長度。擋板只能在x軸或者y軸上,其中每個點都不在坐標軸上
示例
輸入
1 1 2 0 -1 2 -2 1
輸出
0.50000000
由於要計算擋板的最短長度,那么擋板一定是擋住了n-k個,如果擋住了n-k個以上,那么一定可以將擋板長度減少,所以我們判斷n-k就好了,又所有的點都不在坐標軸上就很好辦了。首先確定umi所在位置的象限。很明顯同一象限的點是不可能用擋板擋掉的,對於剩下的點找出線段和 x軸或 y 軸的交點,統計坐標位置。
$kx_{1}+b=y_{1}$
$kx_{2}+b=y_{2}$
可得:
$b=\frac{y_{1}x_{2}-y_{2}x_{1}}{x_{2}-x_{1}}$ $k=\frac{y_{1}-y_{2}}{x_{1}-x_{2}}$
當交點是x軸的時候,我們令y=0,那么$x=-\frac{b}{k}$,交點是y軸的時候就是b了,然后我們交上去就會發現WA了。。。。
在WA了無數發之后我覺得已經沒有什么能改的了,只有精度的問題了,那么在計算坐標點的時候我們盡量減少除法的使用,實際上x,y軸的交點可以更快算出來:
我們知道斜率$k=\frac{y_{1}-y_{2}}{x_{1}-x_{2}}$那么b的值就可以直接隨便帶個點去減了:$b=y_{0}-kx_{0}$
仿照b的求法,我們將式子同時除以k:$x+\frac{1}{k}b=\frac{1}{k}y$ 那么令$y_{0}=0$的時候$x=-\frac{1}{k}b$ 而上面的式子我們又可以算出
$-\frac{1}{k}b=x_{0}-\frac{1}{k}y_{0}$那么答案也就出來了。我們減少了一次除法運算,只計算了一次k的值
然后我們分別對x,y軸上的點進行循環取最小n-k長度的大小。
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; const double esp=1e-7; const double inf=1e10+10; double point_x[mac],point_y[mac]; int cntx,cnty; int same(double x,double y,double x0,double y0) { if (1.0*x/x0>0 && 1.0*y/y0>0) return 1; return 0; } int cross(double x,double y,double x0,double y0)//0->x,1->y,2->x,y { if (1.0*x/x0<0 && 1.0*y/y0<0) return 2; else if (1.0*x/x0<0) return 1; else if (1.0*y/y0<0) return 0; } void deal(double x,double y,double x0,double y0,int pt) { //y=kx+b //double k=1.0*(y-y0)/(x-x0); //double b=1.0*(y0*x-y*x0)/(x-x0);//剛開始int,y0*x會爆 //double cross_x=-b/k; double b=y0-1.0*x0*(y-y0)/(x-x0); double cross_x=x0-1.0*y0*(x-x0)/(y-y0); if (pt==0) point_x[++cntx]=cross_x; else if (pt==1) point_y[++cnty]=b; else { point_x[++cntx]=cross_x; point_y[++cnty]=b; } } int main(int argc, char const *argv[]) { int n,k; double x0,y0; scanf ("%lf%lf",&x0,&y0); scanf ("%d%d",&n,&k); int len_num=n-k; for (int i=1; i<=n; i++){ double x,y; scanf ("%lf%lf",&x,&y); if (same(x,y,x0,y0)) continue; int point=cross(x,y,x0,y0);//0代表交點在x,1代表在y,2代表x,y都有 deal(x,y,x0,y0,point);//找出所有與x,y軸的交點 } sort(point_x+1,point_x+1+cntx); sort(point_y+1,point_y+1+cnty); double ans=inf; for (int i=1; i+len_num-1<=cntx; i++){ double len_len=point_x[i+len_num-1]-point_x[i]; ans=min(ans,len_len); } for (int i=1; i+len_num-1<=cnty; i++){ double len_len=point_y[i+len_num-1]-point_y[i]; ans=min(ans,len_len); } if (fabs(ans-inf)<esp) printf("-1\n"); else printf("%.8f\n",ans); return 0; }
D.hanayo和米飯
題目大意:問你1到n缺了哪一個數,給出n,和n-1個數
示例
輸入
5 2 5 1 3
輸出
4
沒什么好說的,簽到題,每個數標記一下,然后遍歷輸出沒標記的那個數就可以了。
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; int vis[mac]; int main() { int n,x; scanf ("%d",&n); for (int i=1; i<n; i++) scanf("%d",&x),vis[x]=1; for (int i=1; i<=n; i++) if (!vis[i]){ printf("%d\n",i); break; } return 0; }
E.rin和快速迭代
題目大意:$f(x)$為x的因子個數,將f一直迭代下去問迭代到2要多少次例如:$f(12)=6,f(6)=4,f(4)=3,f(3)=2$總共四次
示例
輸入
12
輸出
4
$10^{12}$看起來很多,實際上我們算因子的時候最多只需要循環$10^{6}$次,而每次計算因子的時候都要開根號,所以直接暴力計算因子數所花費的時間並不是很多
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; #define ll long long int f(ll x) { int ans=0; if (x==2) return ans; ans=1; int sum=0; ll m=sqrt(x); if (m*m==x) sum++,m--; for (int i=1; i<=m; i++){ if (x%i==0) sum+=2; } return ans+f(sum); } int main() { ll n; scanf ("%lld",&n); int ans=f(n); printf("%d\n",ans); return 0; }
F.maki和tree
題目大意:給你一棵樹,這個樹有 $n$個頂點, $n-1$ 條邊。每個頂點被染成了白色或者黑色。取兩個不同的點,它們的簡單路徑上有且僅有一個黑色點的取法有多少?
示例
輸入
3 WBW 1 2 2 3
輸出
3
經過一個黑點的路徑有兩種:兩個端點都是白點;其中一個端點是黑點。
因此我們可以先預處理,將每個白點連通塊上的白點個數統計出來。這樣我們就可以得知每個黑點所連接的白點的權值(即連通塊白點數)。
設某黑點連接了 k 個白點,第 i 個白點的權值為 f(i) 。
那么第一種路徑的數量就是$\sum_{i=1}^{k}\sum_{j=i+1}^{k}f(i)*f(j)$如圖所示:

2到其他的白點的有2-4,2-3,2-5,2-6,2-7
接下來就是4和5到其他白點,由於白塊2已經遍歷過了,所以往前找,那么就是2*(1+2)....
第二種就沒什么好說的了,把所以的白點個數加起來就好了。
emmm,不知道為什么段錯誤。然后我把手動循環改成auto就可以了。。蜜汁BUG。注意答案要用long long,被坑了。。。
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; vector<int>g[mac],blk[mac]; int mark[mac],root[mac],sz[mac]; char s[mac]; void dfs(int x,int fa) { sz[x]=1; for (auto v:g[x]){ if (v==fa) continue; dfs(v,x); sz[x]+=sz[v]; } } int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); scanf ("%s",s+1); int cnt=0; for (int i=1; i<=n; i++){ mark[i]=s[i]=='B'; if (mark[i]) root[++cnt]=i; } for (int i=1; i<n; i++){ int u,v; scanf("%d%d",&u,&v); if (mark[u] || mark[v]) { if (mark[u] && mark[v]) continue; if (mark[u]) blk[u].push_back(v);//黑點u的白兒子 else blk[v].push_back(u);//黑點v的白兒子 continue; } g[u].push_back(v); g[v].push_back(u); } int ans=0; for (int i=1; i<=cnt; i++){ int u=root[i]; int sum=0; for (auto v:blk[u]){ dfs(v,0);//以白兒子v為根進行遍歷計算連通塊v的大小 sum+=sz[v]; } ans+=sum; for (auto v:blk[u]){ ans+=sz[v]*(sum-sz[v]); sum-=sz[v]; } } printf("%d\n",ans); return 0; }
G.eli和字符串
題目大意:一個僅由小寫字母組成的字符串。截取一段連續子串使得這個子串包含至少 $k$ 個相同的某個字母。問子串的長度最小值是多少?
輸入
5 2 abeba
輸出
3
看一下題目。。。秒出二分,至於怎么求區間相同字母的個數,直接用前綴和就好了時間復雜度$O(26n)$。加上二分的log就是$O(logn*26n)$
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; #define ll long long const int mac=2e5+10; const int inf=1e9+10; char s[mac]; int dp[mac][30]; int ok(int x,int n,int k) { for (int i=1; i+x-1<=n; i++){ int p=-1; for (int j=0; j<='z'-'a'; j++){ p=max(dp[i+x-1][j]-dp[i-1][j],p); } if (p>=k) return 1; } return 0; } int main() { int n,k; scanf ("%d%d",&n,&k); scanf ("%s",s+1); int l=1,r=n,mid,ans=inf; for (int i=1; i<=n; i++) for (int j=0; j<='z'-'a'; j++){ dp[i][j]=dp[i-1][j]+(s[i]=='a'+j); } while (l<=r){ int mid=(l+r)>>1; if (ok(mid,n,k)){ ans=mid; r=mid-1; } else l=mid+1; } if (ans==inf) printf("-1\n"); else printf("%d\n",ans); return 0; }
H.nozomi和字符串
題目大意:給你一個字符串(只包含01)你有k次將變化字母的機會,你要找一個盡量長的子串,使得你能夠在k次操作以內將其全部變成一樣的字母,問最長的子串長度
輸入
5 1 10101
輸出
3
這題也是一眼二分,和G題一樣的,搞個前綴和維護一下就好了
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; const int mac=2e5+10; char s[mac]; int dp[mac][2]; int ok(int x,int k,int n) { for (int i=1; i+x-1<=n; i++){ if (dp[i+x-1][0]-dp[i-1][0]<=k) return 1; if (dp[i+x-1][1]-dp[i-1][1]<=k) return 1; } return 0; } int main() { int n,k; scanf ("%d%d",&n,&k); scanf ("%s",s+1); for (int i=1; i<=n; i++){ for (int j=0; j<=1; j++){ dp[i][j]=dp[i-1][j]+(s[i]=='0'+j); } } int l=1,r=n,mid,ans=-1; while (l<=r){ mid=(l+r)>>1; if (ok(mid,k,n)){ ans=mid; l=mid+1; } else r=mid-1; } printf("%d\n",ans); return 0; }
I.nico和niconiconi
題目大意:給你一字符串,其中$nico$得a分,$niconi$得b分,$niconiconi$得c分,其中字符不可重復使用,問你最多能得多少分
示例
輸入
19 1 2 5 niconiconiconiconi~
輸出
7
這題一眼dp,狀態轉移也很好寫:
$if (sbtring(i-3,i)==nico) dp[i]=max(dp[i],dp[i-4]+a)$
$if (sbtring(i-5,i)==niconi) dp[i]=max(dp[i],dp[i-6]+b)$
$if (sbtring(i-9,i)==niconiconi) dp[i]=max(dp[i],dp[i-10]+c)$
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; #define ll long long const int mac=3e5+10; char s[mac]; ll dp[mac]; string s1,s2,s3; int ok(int x,string ss) { int len=ss.length(); int cnt=0; if (x<len) return 0; for (int i=x-len+1; i<=x; i++){ if (s[i]!=ss[cnt++]) return 0; } return 1; } int main(int argc, char const *argv[]) { int n,a,b,c; scanf ("%d%d%d%d",&n,&a,&b,&c); scanf("%s",s+1); s1="nico";s2="niconi";s3="niconiconi"; for (int i=1; i<=n; i++){ dp[i]=dp[i-1]; if (ok(i,s1)) dp[i]=max(dp[i],dp[i-4]+a); if (ok(i,s2)) dp[i]=max(dp[i],dp[i-6]+b); if (ok(i,s3)) dp[i]=max(dp[i],dp[i-10]+c); } printf("%lld\n",dp[n]); return 0; }
J.u's的影響力
題目大意:$f(i)=f(i-1)*f(i-2)*a^{b}$,其中$f(1)=x,f(2)=y$,問$f(n)$。重點是$n,x,y,a,b<=10^{12}$。取模1e9+7
示例
輸入
4 2 3 2 1
輸出
72
這一題才是重頭戲。。。
我們可以先找規律:
$f(1)=x,f(2)=y,f(3)=xya^{b},f(4)=xy^{2}a^{2b}$
$f(5)=x^{2}y^{3}a^{4b},f(6)=x^{3}y^{5}a^{7b},f(7)=x^{5}y^{8}a^{12b}$
.....
很明顯我們可以x,y,a的冪是滿足斐波那契數列的變形,
其中x和y的冪滿足$f(i)=f(i-1)+f(i-2)$ a的冪滿足$f(i)=f(i-1)+f(i-2)+b$
那么我們將每個冪算出來就好了(一個簡單的矩陣快速冪)。。。。然后你們發現冪太大了,存不下(難道取模嗎?)
。。。。冪如果取模的話好像有問題,不過注意這里的模數是1e9+7,是個素數,我們根據費馬小定理$a^{p-1}\equiv 1(modp)$
那么有$a^{1e9+6}\equiv 1(mod 1e9+7)$
也就是說我們可以直接對冪的(1e9+6)取模。。。。好像不用歐拉降冪了
這里的矩陣也很簡單x的冪是$f(1)=1,f(2)=0$,y的冪是$f(1)=0,f(2)=1$,a的冪是$f(1)=0,f(2)=0,f(3)=b$
那么三個矩陣也很好寫出來了:
這是x的冪
這是y的冪
這是a的冪
以下是AC代碼:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod=1e9+7; int up; struct Mat { ll m[5][5]; Mat(){memset(m,0,sizeof m);} }; ll qick(ll a,ll b) { ll ans=1; a%=mod; while (b){ if (b&1) ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } Mat multi(Mat a,Mat b) { Mat ans; for (int i=1; i<=up; i++) for (int j=1; j<=up; j++){ for (int k=1; k<=up; k++){ ans.m[i][j]+=a.m[i][k]*b.m[k][j]%(mod-1); ans.m[i][j]%=(mod-1); } } return ans; } Mat qick_mat(Mat a,ll n) { Mat ans; for (int i=1; i<=up; i++) ans.m[i][i]=1; while (n){ if (n&1) ans=multi(ans,a); a=multi(a,a); n>>=1; } return ans; } int main(int argc, char const *argv[]) { ll n,x,y,a,b; cin>>n>>x>>y>>a>>b; if (n==1){cout<<x%mod<<endl;return 0;} else if (n==2){cout<<y%mod<<endl;return 0;} else if (x%mod==0 || y%mod==0 || a%mod==0) {cout<<0<<endl;return 0;}//注意!!! else { up=2; Mat mx,star_mx; mx.m[1][1]=mx.m[1][2]=mx.m[2][1]=1; star_mx.m[2][1]=1; mx=qick_mat(mx,n-2); mx=multi(mx,star_mx); ll ans1=qick(x,mx.m[1][1]); //cout<<ans1<<endl; Mat my,star_my; my.m[1][1]=my.m[1][2]=my.m[2][1]=1; star_my.m[1][1]=1; my=qick_mat(my,n-2); my=multi(my,star_my); ll ans2=qick(y,my.m[1][1]); //cout<<ans2<<endl; up=3; Mat ma,star_ma; ma.m[1][1]=ma.m[1][2]=ma.m[1][3]=1; ma.m[2][1]=ma.m[3][3]=1; star_ma.m[1][1]=b%(mod-1);star_ma.m[2][1]=0;star_ma.m[3][1]=b%(mod-1); ma=qick_mat(ma,n-3); ma=multi(ma,star_ma); ll ans3=qick(a,ma.m[1][1]); //cout<<ans3<<endl; ll ans=(ans1*ans2%mod*ans3)%mod; cout<<ans<<endl; } return 0; }
