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;
}