A:串 (DP)
题目大意是有多少种长度不超过 n n n的字符串能包含一个子序列“us”。这道题一开始我的想法是用dp的方式表示①包括"us"的子串;②只包括“u”不包括“s”的子串;③不包括“u”的子串。但是发现这三种情况的和不等于 2 6 n 26^n 26n,原因在于第二种情况遗漏了"s"在“u”前面的情况(既没有形成“us”,也包括了“u”)。因此我们把第二种情况更改成没有包括“us”但包括了"u"。
我们令 d p [ i ] [ 0 / 1 / 2 ] dp[i][0/1/2] dp[i][0/1/2]对应三种情况,则 d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] ∗ 26 + d p [ i − 1 ] [ 1 ] , d p [ i ] [ 1 ] = 2 6 i − d p [ i ] [ 0 ] − d p [ i ] [ 2 ] , d p [ i ] [ 2 ] = 2 5 i dp[i][0]=dp[i-1][0]*26+dp[i-1][1],dp[i][1]=26^i-dp[i][0]-dp[i][2],dp[i][2]=25^i dp[i][0]=dp[i−1][0]∗26+dp[i−1][1],dp[i][1]=26i−dp[i][0]−dp[i][2],dp[i][2]=25i。补充一个转移时遇到的错误:给 d p [ i ] [ 0 ] dp[i][0] dp[i][0]多加了一项 d p [ i − 2 ] [ 2 ] dp[i-2][2] dp[i−2][2](意为直接在后面补"us")。这样的错误在于我们能从情况②中找到第 i − 1 i-1 i−1位是“u”,前面跟 d p [ i − 2 ] [ 2 ] dp[i-2][2] dp[i−2][2]的排列是一样的字符串,造成重复计数。
#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e6+100;
const int mod=1e9+7;
typedef long long ll;
ll dp[maxn][3];
int main()
{
close;int n;cin>>n;
dp[1][1]=1;dp[1][2]=25;
dp[2][0]=1;dp[2][1]=50;dp[2][2]=625;
ll sum=1,mul=26*26;
for(int i=3;i<=n;++i)
{
mul=mul*26%mod;
dp[i][0]=(dp[i-1][0]*26%mod+dp[i-1][1])%mod;
dp[i][2]=dp[i-1][2]*25%mod;
dp[i][1]=((mul-dp[i][0]-dp[i][2])%mod+mod)%mod;
sum=(sum+dp[i][0])%mod;
}
cout<<sum<<endl;
}
B:括号 (构造)
题目大意是构造一个非空的仅仅包括’(‘和’)‘的括号字符串,包含正好 k k k个不同合法括号对,但构造的括号字符串的长度不能超过1e5。
我的想法是:假设我们构造一个类似于"()()()()…",那么里面的合法括号对的个数就是1+2+3+…;假设我们现在有5e4对括号,那么应该有(5e4-1)*5e4/2>1e9,因此这样的构造不会超出长度限制。但是1~x的和不一定恰好是 k k k,我们只需要找到一个和 s u m sum sum不超过 k k k的最大的 x x x,那么一定有 k − s u m ≤ x k-sum\le x k−sum≤x,我们只要多加一个’)'就能实现。注意特判0的情况(要求构造的括号序列非空)。
#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e5+100;
int num[maxn];
int main()
{
close;int n;cin>>n;
for(int i=1;n>0;++i)
{
if(n>=i) n-=i,num[i]++;
else {
num[n]++;break;}
}
for(int i=1;num[i]!=0;++i)
{
if(num[i]==1) cout<<"()";
else if(num[i]==2) cout<<"())";
}
if(n==0) cout<<")";
}
官方提供的题解是找到 p = k p=\sqrt {k} p=k,令 a , b a,b a,b满足 k = p ∗ a + b ( b < p ) k=p*a+b(b<p) k=p∗a+b(b<p).那么我们可以构造 p p p对左括号和 a a a对右括号,并在第 b b b个左括号的右边插入一个右括号。这样的构造也能满足题意。
C:红和蓝(DFS)
题目大意是给定一棵树,每个结点只能被染成红色或蓝色,同时要求对于结点 x x x,与他相邻的结点中有且仅有一个与其染成相同颜色的结点,问能否找到一个合法的染色方式。
很明显我们能发现叶子结点是非常特殊的结点,因为叶子结点的颜色一定和他的父结点的颜色相同。我们的做法是:两遍DFS,第一遍将结点两两分组,叶子结点或者没分组的结点都和他的父亲结点分为一组(根据递归性质,子结点已经分完组,如果没有给父亲结点染上颜色,父亲结点只能跟其父亲结点分为一组);第二遍DFS在确定了能够有合法染色方案的前提上,再次进行染色。这里指出一个自己编程的错误:不能对叶子结点直接染一种颜色然后往回判断!例如数据“4 1 2 2 3 1 4”.
#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
const int maxn=1e5+100;
using namespace std;
vector<int> v[maxn];
int group[maxn],color[maxn],cnt=0;bool ok=true;
void DFS1(int root,int fa)
{
int son=0;
for(int i=0;i<v[root].size();++i)
{
if(v[root][i]==fa) continue;
else son++,DFS1(v[root][i],root);
}
if(son==0 || group[root]==0)
{
if(group[fa]!=0) ok=false;
else group[root]=group[fa]=++cnt;
}
}
void DFS2(int root,int fa)
{
for(int i=0;i<v[root].size();++i)
{
if(v[root][i]==fa) continue;
else if(group[v[root][i]]==group[root]) color[v[root][i]]=color[root];
else color[v[root][i]]=(1^color[root]);
DFS2(v[root][i],root);
}
}
int main()
{
close;int n;cin>>n;
for(int i=1;i<n;++i)
{
int x,y;cin>>x>>y;
v[x].push_back(y);v[y].push_back(x);
}
DFS1(1,0);
if(!ok || group[0]) {
cout<<-1<<endl;return 0;}
color[1]=1;DFS2(1,0);
for(int i=1;i<=n;++i) cout<<(color[i]?'R':'B');
}
D:点一成零(连通块 并查集)
题目大意是给定一个 n ∗ n n*n n∗n的01矩阵,然后有 k k k次操作,每次操作后都问你如何将全部的1变成0。这里变化的方案是,你选择了数字1连通块中的一个1变成0,就可以将整个连通块中的1都变成0。
很明显这个题的答案应该是 n ! ∗ ∏ i = 1 n s i z e ( i ) n!*\prod_{i=1}^n size(i) n!∗∏i=1nsize(i),其中 n n n表示连通块的个数, s i z e ( i ) size(i) size(i)表示第 i i i个连通块的大小。很明显我们可以用并查集去维护连通块的个数及大小,这里放一下自己遇到的坑点:①数组有没有越界;②周围要合并的会不会本身就是一个连通块;③使用num数组表示当前连通块的大小是一定要先find_set()找到根节点。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=500*500+100;
const int mod=1e9+7;
ll fact[maxn];int a[510][510],s[maxn],num[maxn];
ll inv(ll base,ll x)
{
ll ans=1;
while(x) {
if(x&1)ans=ans*base%mod;base=base*base%mod;x>>=1;}
return ans;
}
void prepare(){
fact[1]=1;for(int i=2;i<maxn;++i) fact[i]=fact[i-1]*i%mod;}
void init_set(){
for(int i=1;i<maxn;++i) s[i]=i,num[i]=1;}
int find_set(int x){
if(x!=s[x]) s[x]=find_set(s[x]);return s[x];}
void union_set(int x,int y){
x=find_set(x);y=find_set(y);if(x!=y) s[x]=s[y],num[y]+=num[x];}
ll solve(int org,int now) {
ll rec=num[find_set(org)];union_set(org,now);return rec;}
int main()
{
int n;scanf("%d",&n);
for(int i=0;i<n;++i)
{
getchar();
for(int j=0;j<n;++j)
{
char c=getchar();a[i][j]=c-'0';}
}
prepare();
init_set();
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
if(a[i][j]==1){
if(i-1>=0 && a[i-1][j]==1) union_set(i*n+j+1,(i-1)*n+j+1);
if(j-1>=0 && a[i][j-1]==1) union_set(i*n+j+1,i*n+j);
}
ll tot=0,ans=1;
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
if(a[i][j]==1 && find_set(i*n+j+1)==i*n+j+1) tot++,ans=ans*num[find_set(i*n+j+1)]%mod;
int k;scanf("%d",&k);
for(int i=1;i<=k;++i)
{
int x,y,rec=1;scanf("%d%d",&x,&y);
if(a[x][y]==1) {
printf("%lld\n",ans*fact[tot]%mod);continue;}
ll tmp=1;num[x*n+y+1]=1;a[x][y]=1;
if(x-1>=0 && a[x-1][y]==1 && find_set((x-1)*n+y+1)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve((x-1)*n+y+1,x*n+y+1)%mod;
if(x+1<n && a[x+1][y]==1 && find_set((x+1)*n+y+1)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve((x+1)*n+y+1,x*n+y+1)%mod;
if(y-1>=0 && a[x][y-1]==1 && find_set(x*n+y)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve(x*n+y,x*n+y+1)%mod;
if(y+1<n && a[x][y+1]==1 && find_set(x*n+y+2)!=find_set(x*n+y+1)) rec--,tmp=tmp*solve(x*n+y+2,x*n+y+1)%mod;
tot+=rec;ans=ans*inv(tmp,mod-2)%mod*num[find_set(x*n+y+1)]%mod;
printf("%lld\n",ans*fact[tot]%mod);
}
}
E:三棱锥之刻(计算几何)
题目大意就是求解圆心在正三棱锥的中心,半径为 r r r的球与正棱锥的交面积。
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-3;
const double PI=acos(-1);
int main()
{
double a,r;scanf("%lf%lf",&a,&r);
if(sqrt(6)*a/12-r>=-eps) printf("0.00000");
else if(sqrt(2)*a/4-r>=-eps) printf("%.5f",4*PI*(r*r-a*a/24));
else if(sqrt(6)*a/4-r>=eps){
double r1=sqrt(r*r-a*a/24);
double r2=sqrt(r1*r1-a*a/12);
printf("%.5f",4*(PI*r1*r1-acos(sqrt(3)*a/(6*r1))*3*r1*r1+r2*a*sqrt(3)/2));
}
else printf("%.5f",sqrt(3)*a*a);
}
F:对答案一时爽(思维)
得分之和最小就是两个人的答案全是错误的;得分之和最大就是一个人的答案是全部正确的,那么同时另一个人和他作答相同的题目也会得分。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,sum=0;cin>>n;
string A,B;
getchar();getline(cin,A);getline(cin,B);
for(int i=0;i<n*2;i+=2) if(A[i]==B[i]) sum++;
cout<<n+sum<<' '<<0;
}
G:好玩的数字游戏(大模拟)
#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int a[10][10],tmp[10][10];
typedef long long ll;
ll x,p,q;
long long f(long long x,int p){
long long r=(x%p)*(x%p)/10%p;
return (r^(1LL<<17)^(1LL<<57))%p;
}
int solve(char op,int loc)
{
if(op=='D'){
int score=0;
vector<int> before,after;before.clear();after.clear();
for(int i=4;i>0;--i) if(a[loc][i]!=0) before.push_back(a[loc][i]);
int size=before.size();
if(size==0) return 0;
else{
int l=0,r=1;
while(r<size)
{
if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
else after.push_back(before[l]),l++,r++;
}
if(l<size) after.push_back(before[l]);
int cur=after.size(),p=0;
for(int i=4;i>0;--i) a[loc][i]=(p<cur)?after[p++]:0;
return score;
}
}
else if(op=='A'){
int score=0;
vector<int> before,after;before.clear();after.clear();
for(int i=1;i<=4;++i) if(a[loc][i]!=0) before.push_back(a[loc][i]);
int size=before.size();
if(size==0) return 0;
else{
int l=0,r=1;
while(r<size)
{
if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
else after.push_back(before[l]),l++,r++;
}
if(l<size) after.push_back(before[l]);
int cur=after.size(),p=0;
for(int i=1;i<=4;++i) a[loc][i]=(p<cur)?after[p++]:0;
return score;
}
}
else if(op=='W'){
int score=0;
vector<int> before,after;before.clear();after.clear();
for(int i=1;i<=4;++i) if(a[i][loc]!=0) before.push_back(a[i][loc]);
int size=before.size();
if(size==0) return 0;
else{
int l=0,r=1;
while(r<size)
{
if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
else after.push_back(before[l]),l++,r++;
}
if(l<size) after.push_back(before[l]);
int cur=after.size(),p=0;
for(int i=1;i<=4;++i) a[i][loc]=(p<cur)?after[p++]:0;
return score;
}
}
else{
int score=0;
vector<int> before,after;before.clear();after.clear();
for(int i=4;i>0;--i) if(a[i][loc]!=0) before.push_back(a[i][loc]);
int size=before.size();
if(size==0) return 0;
else{
int l=0,r=1;
while(r<size)
{
if(before[l]==before[r]) score+=before[l]*2,after.push_back(before[l]*2),l+=2,r+=2;
else after.push_back(before[l]),l++,r++;
}
if(l<size) after.push_back(before[l]);
int cur=after.size(),p=0;
for(int i=4;i>0;--i) a[i][loc]=(p<cur)?after[p++]:0;
return score;
}
}
}
bool change(char op,int loc)
{
if(op=='D' || op=='A'){
for(int i=1;i<=4;++i) if(a[loc][i]!=tmp[loc][i]) return true;
return false;
}
else{
for(int i=1;i<=4;++i) if(a[i][loc]!=tmp[i][loc]) return true;
return false;
}
}
void get_newboard()
{
bool ok=true;
while(ok){
int tmp=x%16,cur_x=tmp/4+1,cur_y=tmp%4+1;
if(a[cur_x][cur_y]==0) ok=false,a[cur_x][cur_y]=2;
x=f(x,p);
}
}
bool Game_over()
{
for(int i=1;i<=4;++i)
for(int j=1;j<=3;++j)
if(a[i][j]==0 || a[i][j+1]==0 || a[i][j]==a[i][j+1] || a[j][i]==0 || a[j+1][i]==0 || a[j][i]==a[j+1][i]) return true;
return false;
}
void get_sameboard(){
for(int i=1;i<=4;++i) for(int j=1;j<=4;++j) tmp[i][j]=a[i][j];}
int main()
{
close;cin>>x>>p>>q;
for(int i=1;i<=4;++i)
for(int j=1;j<=4;++j)
cin>>a[i][j],tmp[i][j]=a[i][j];
string op;cin>>op;
ll ans=0,lastpos=-1;
for(int i=0;i<q;++i)
{
ans+=solve(op[i*2],op[i*2+1]-'0');
if(change(op[i*2],op[i*2+1]-'0')) get_newboard(),get_sameboard();
if(!Game_over()) {
lastpos=i+1;break;}
}
if(lastpos==-1) cout<<ans<<endl<<"never die!";
else cout<<ans<<endl<<lastpos;
}
H:幂塔个位数的计算
题目大意就是让你求幂塔的个位数,但是需要注意的是 1 ≤ n ≤ 1 0 100000 1\le n\le 10^{100000} 1≤n≤10100000。
需要掌握的一个非常重要的定理:欧拉降幂: a b ≡ { a b % Φ ( p ) g c d ( a , p ) = 1 a b g c d ( a , p ) ≠ 1 , b < Φ ( p ) ( m o d p ) a b % Φ ( p ) + Φ ( p ) g c d ( a , p ) ≠ 1 , b ≥ Φ ( p ) a^b\equiv \begin{cases} \ a^{b\%\Phi (p)} & gcd(a,p)=1\\ a^{b} & gcd(a,p)\ne 1,b<\Phi(p) & (mod p)\\ a^{b\% \Phi(p)+\Phi(p)} & gcd(a,p)\ne 1,b\ge \Phi(p)\\ \end{cases} ab≡⎩⎪⎨⎪⎧ ab%Φ(p)abab%Φ(p)+Φ(p)gcd(a,p)=1gcd(a,p)=1,b<Φ(p)gcd(a,p)=1,b≥Φ(p)(modp)我们在不知道a,p是否互质的时候,可以直接使用第三个公式。同时降幂的过程中对于 m o d = 10 mod=10 mod=10来说只需要降4次就能到达1,所以效率还是很高的。
def phi(n):
if(n==10):return 4
elif(n==4):return 2
else:return 1
def eular(a,n,mod):
if(n==1): return a%mod
elif(mod==1): return 0
else:
e=eular(a,n-1,phi(mod))
return pow(a,e+phi(mod),mod)
a=int(input())
n=int(input())
if(n==1):print(a%10)
else:print(eular(a,n,10))
I:限制不互素对的排列(构造)
题目大意就是重新排列1~n这些数字,使得一共有 k k k对相邻的数字满足他们的gcd大于1。
这个题因为限制了 0 ≤ k ≤ n / 2 0\le k \le n/2 0≤k≤n/2,我们能够使用常见的结论:相邻两个偶数的gcd一定大于1,相邻两个奇数的gcd一定等于1。 n n n个数有 ⌊ n / 2 ⌋ \lfloor n/2 \rfloor ⌊n/2⌋个偶数,最多有 ⌊ n / 2 ⌋ − 1 \lfloor n/2 \rfloor -1 ⌊n/2⌋−1对数满足题意,如果 k = = n / 2 k==n/2 k==n/2,这时候我们就根据最后一个偶数能不能被3整除,补上一个单独的3或者补上一对3 9来凑齐。注意某些情况的特判。
#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e5+100;
int vis[maxn];
int main()
{
close;int n,k,num=0,last;cin>>n>>k;
if(n<6 && k==(n/2)) {
cout<<-1<<endl;}
else if(n==6 && k==3) {
cout<<"2 4 6 3 1 5"<<endl;}
else if(n==7 && k==3) {
cout<<"2 4 6 3 1 5 7"<<endl;}
else if(n==8 && k==4) {
cout<<"2 4 8 6 3 1 5 7"<<endl;}
else{
cout<<2;vis[2]=1;
for(int i=4;i<=n&&num<k;i+=2)
cout<<' '<<i,num++,vis[i]=1,last=i;
if(num<k){
if(last%3==0) cout<<' '<<3,vis[3]=1;
else cout<<' '<<3<<' '<<9,vis[3]=vis[9]=1;
}
for(int i=1;i<=n;++i) if(!vis[i]) cout<<' '<<i,vis[i]=1;
}
}
J:一群小青蛙呱蹦呱蹦呱(数论)
题目大意是无穷多只青蛙按照 1 , p ( i ) , p ( i ) 2 , p ( i ) 3 . . . 1,p(i),p(i)^2,p(i)^3... 1,p(i),p(i)2,p(i)3...跳跃,其中 p ( i ) p(i) p(i)表示第 i i i个质数。问最终1~n中所有青蛙跳不到的数字的lcm是多少(由于答案很大,要对1e9+7取模)。
这个题很容易发现青蛙跳不到的数字就是素因素个数超过1个的合数,再根据lcm的定义, l c m = ∏ i = 1 t o t p ( i ) m a x ( c n t 1 , c n t 2 , . . . ) lcm=\prod_{i=1}^{tot}p(i)^{max(cnt_1,cnt_2,...)} lcm=∏i=1totp(i)max(cnt1,cnt2,...),其中 c n t cnt cnt代表上述合数中该素数因子的出现次数, t o t tot tot表示1~n中所有的质数个数。进一步发现, p ( i ) = = 2 p(i)==2 p(i)==2时, c n t m a x = m a x { k ∣ 2 k ∗ 3 ≤ n } cnt_{max}=max\{k|2^k*3\le n\} cntmax=max{
k∣2k∗3≤n};否则, c n t m a x = m a x { k ∣ p ( i ) k ∗ 2 ≤ n } cnt_{max}=max\{k|p(i)^k*2\le n\} cntmax=max{
k∣p(i)k∗2≤n}。我们去计算各个素数最大的 c n t m a x cnt_{max} cntmax,用循环的方式去寻找,根据素数个数定理,大约是 n / l n ( n ) n/ln(n) n/ln(n)个素数,同时根据调和级数,时间复杂度近似线性。同时找质数用欧拉筛,时间复杂度也是 O ( n ) O(n) O(n)。最后注意特判即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn=8e7+100;
const int mod=1e9+7;
bool u[maxn];int su[maxn],num=0;
typedef long long ll;
void prepare(int n)
{
memset(u,true,sizeof(u));
for(int i=2;i<=n;++i)
{
if(u[i]) su[++num]=i;
for(int j=1;j<=num;++j)
{
if(i*su[j]>n) break;
u[i*su[j]]=false;
if(i%su[j]==0) break;
}
}
}
int main()
{
int n;cin>>n;
if(n<=5) {
cout<<"empty"<<endl;return 0;}
prepare(n/2);ll ans=1;
for(int i=1;i<=num;++i)
{
ll bound=(su[i]==2?n/3:n/2),cur=1;
while(cur<=bound) cur*=su[i];
cur/=su[i];
ans=ans*cur%mod;
}
cout<<ans;
}