gzhu happy weekly contest 3
2021 Shandong Provincial Collegiate Programming Contest F. Birthday Cake
题意:给出n个串,问有多少个对字符串相连之后,从中间分开后形成的两个字符串相等。
思路:如果当前的字符串是答案贡献中的某一种情况中较长的那一个字符串,那么这个字符串的\(头(1,i)尾(n-i+1,n)\)一定相同
举个例子:
ab
cabc
那么可以对这个截去头尾剩下的那一段进行判断
过程需要使用双哈希!!!
#include<bits/stdc++.h>
#define pii pair<int,int>
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define pb push_back
#define de cout<<"---"<<endl;
using namespace std;
const int inf =0x3f3f3f;
const ll mod =1e9+7;
const int N=1e6+7;
ll qpow(ll a,ll p){ll ans=1;while(p){if(p&1)ans=ans*a%mod;a=a*a%mod;p>>=1;}return ans%mod;}
ull mod1=1e9+7,mod2=1e9+11;
ull h1[N],h2[N],p1[N],p2[N];
ull base=131;
ull get1(int l,int r){
return (h1[r]-h1[l-1]*p1[r-l+1]%mod1+mod1)%mod1;
}
ull get2(int l,int r){
return (h2[r]-h2[l-1]*p2[r-l+1]%mod2+mod2)%mod2;
}
pair<ull,ull>douhash(int l,int r){
return {get1(l,r),get2(l,r)};
}
void init(){
p1[0]=1,p2[0]=1;
for(int i=1;i<N;i++){
p1[i]=(p1[i-1]*base)%mod1;
p2[i]=(p2[i-1]*base)%mod2;
}
}
map<pair<ull,ull>,ull>all;
map<ull,pair<ull,ull>>mp;
int num;
void solve(){
init();
int m;cin>>m;
for(int k=1;k<=m;k++){
char s[N];
cin>>s+1;
int n=strlen(s+1);
for(int i=1;i<=n;i++){
h1[i]=((h1[i-1]*base%mod1)+(s[i]-'a'+1))%mod1;
h2[i]=((h2[i-1]*base%mod2)+(s[i]-'a'+1))%mod2;
}
all[{h1[n],h2[n]}]++;
for(int i=1;i+i<n;i++){
if(douhash(1,i)==douhash(n-i+1,n)){
mp[++num]=douhash(i+1,n-i);
}
}
}
ll ans=0;
for(int i=1;i<=num;i++){
ans+=all[mp[i]];
}
for(auto i:all){
ans+=((i.second-1)*i.second)/2;
}
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int kase=1;
// cin>>kase;
while(kase--){solve();}
}
CodeForces - 1561D2 Up the Strip
题意:从n走到1,每一步有两种走法,第一种是从\([x+1,n]\)走到 \(x\),第二种是当前位置\([x*j,x*j+j-1]\)中的一个数走到\(x\),问都到位置1的方案数。
思路:dp状态转移方程
\(dp[i]=\sum_{j=i+1}^{n}dp[j]+\sum_{j=1}^{i*j<=n}\sum_{k=i*j}^{i*j+j-1}dp[k]\)
利用前缀和\(pre\)进行优化
但由于下面的代码是从n走到1,所以此处\([x*j,x*j+j-1]\),x*j
的方案数一定比x*j+j-1
大,因为x*j
是由后者转移过来。
所以此处的 \(ri表示的是x*j,le表示的是x*j+j-1\)
\(区间和应该表示为pre[ri]-pre[le+1],而不是pre[ri]-pre[le-1],因为前文已经提到是由n走到1\)
#include<bits/stdc++.h>
#define pii pair<int,int>
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define pb push_back
#define de cout<<"---"<<endl;
using namespace std;
const int inf =0x3f3f3f;
const ll mod =1e9+7;
const int N=1e6+7;
ll qpow(ll a,ll p){ll ans=1;while(p){if(p&1)ans=ans*a%mod;a=a*a%mod;p>>=1;}return ans%mod;}
ll dp[4*N];
ll pre[4*N];
void solve(){
int n,m;cin>>n>>m;
dp[n]=1;
pre[n]=1;
for(int i=n-1;i>=1;i--){
dp[i]=(dp[i]+pre[i+1])%m;
for(int j=2;i*j<=n;j++){
int ri=i*j;
int le=min(i*j+j-1,n);
dp[i]=(dp[i]+pre[ri]-pre[le+1])%m;
}
pre[i]=(pre[i+1]+dp[i])%m;
}
cout<<dp[1]%m<<endl;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int kase=1;
// cin>>kase;
while(kase--){solve();}
}
The 15th Chinese Northeast Collegiate Programming Contest C. Vertex Deletion
题意:给出一棵树。定义一棵好树:删除一些点后完之后,任意一个点都应该有另外一个点与之相连。问删除的方案数。
思路:树形dp
\[dp[u][0]表示已u为根并且删去u的合法方案数 \\
dp[u][1]表示已u为根并且删完后u至少仍然有一个儿子v的合法方案数\\
dp[u][2]表示已u为根并且删去u的所有儿子v的合法方案数\\
dp[u][0]=\prod_{v\in{son_u}}(dp[v][0]+dp[v][1])\\
dp[u][1]=\prod_{v\in{son_u}}(dp[v][0]+dp[v][1]+dp[v][2])-dp[u][2]\\
上式属于容斥,即儿子所有的合法状态减去儿子都被删去的状态\\
dp[u][2]=\prod_{v\in{son_v}}dp[v][0]\\
若以1为根,答案就是dp[1][0]+dp[1][1]
\]
#include<bits/stdc++.h>
#define pii pair<int,int>
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define pb push_back
#define de cout<<"---"<<endl;
using namespace std;
const int inf =0x3f3f3f;
const ll mod =998244353;
const int N=1e5+7;
ll qpow(ll a,ll p){ll ans=1;while(p){if(p&1)ans=ans*a%mod;a=a*a%mod;p>>=1;}return ans%mod;}
ll dp[N][3];
vector<int>g[N];
void dfs(int u,int fa){
dp[u][0]=dp[u][1]=dp[u][2]=1;
for(auto v:g[u]){
if(v==fa)continue;
dfs(v,u);
dp[u][0]=(dp[u][0]*(dp[v][1]+dp[v][0])%mod)%mod;
dp[u][1]=(dp[u][1]*(dp[v][2]+dp[v][1]+dp[v][0])%mod)%mod;
dp[u][2]=(dp[u][2]*dp[v][0])%mod;
}
dp[u][1]=(dp[u][1]-dp[u][2]+mod)%mod;
}
void solve(){
int n;cin>>n;
for(int i=1;i<=n;i++){
g[i].clear();
}
for(int i=1;i<n;i++){
int u,v;cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
cout<<(dp[1][0]+dp[1][1])%mod<<endl;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int kase=1;
cin>>kase;
while(kase--){solve();}
}
Codeforces Round #681 E. Long Permutation
前置知识:康托展开,逆康托展开
最多排列次数最多为\(10^{10}\),\(15!>10^{10}\)故只需要变换最后15位数即可。
#include<bits/stdc++.h>
#define pii pair<int,int>
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define pb push_back
#define de cout<<"---"<<endl;
using namespace std;
const int inf =0x3f3f3f;
const ll mod =998244353;
const int N=2e5+7;
ll qpow(ll a,ll p){ll ans=1;while(p){if(p&1)ans=ans*a%mod;a=a*a%mod;p>>=1;}return ans%mod;}
ll n,q;
ll pre[N];
ll fac[N];
void solve(){
cin>>n>>q;
fac[0]=1;
for(int i=1;i<=16;i++){
fac[i]=fac[i-1]*i;
}
// cout<<fac[15]<<endl;
for(int i=1;i<=n;i++){
pre[i]=pre[i-1]+i;
}
ll sum=0;
while(q--){
int op;cin>>op;
if(op==1){
int l,r;cin>>l>>r;
cout<<pre[r]-pre[l-1]<<endl;
}
else {
int x;cin>>x;
ll temp=x+sum;
vector<int>v,ans;
for(int i=n-min(15ll,n)+1;i<=n;i++){
v.push_back(i);
}
for(int i=min(15ll,n)-1;i>=0;i--){
ll num1=temp%fac[i];
ll num2=temp/fac[i];
temp=num1;
ans.push_back(v[num2]);
v.erase(v.begin()+num2);
}
int index=0;
for(int i=n-min(15ll,n)+1;i<=n;i++){
pre[i]=pre[i-1]+ans[index++];
}
sum+=x;
}
}
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int kase=1;
// cin>>kase;
while(kase--){solve();}
}