牛客网暑期ACM多校训练营(第一场
补得好辛苦QAQ,不过终于补完了。。
A Monotonic Matrix
A题博主是打表找出规律来的,稍后会把详解贴上来。
找出来的规律为:C(m+n,n)*C(m+n+1,n)/(n+1)
找到规律就好办了,直接预处理出阶乘及阶乘的逆元及1到maxn的逆元就行了。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int mod=1e9+7;
long long tab1[maxn],tab2[maxn];
long long extgcd(long long a,long long b,long long &x,long long &y)
{
if(b==0){x=1;y=0;return a;}
long long d=extgcd(b,a%b,x,y);
long long t=x;x=y;y=t-a/b*y;
return d;
}
long long mod_reverse(long long a,long long p)
{
long long x,y;
long long d=extgcd(a,p,x,y);
if(d==1)return (x%p+p)%p;
return -1;
}
void init()
{
tab1[1]=1;
tab2[1]=mod_reverse(1,mod);
for(int i=2;i<maxn;i++)
tab1[i]=tab1[i-1]*i%mod,tab2[i]=mod_reverse(tab1[i],mod);
tab2[0]=1;
}
int C(int a,int b)
{
return 1LL*tab1[a]*tab2[b]%mod*tab2[a-b]%mod;
}
int inv[maxn];
void invTable(int n,int p)
{
inv[1]=1;
for(int i=2;i<=n;i++)
{
inv[i]=1LL*(p-p/i)*inv[p%i]%p;
}
}
int main()
{
init();
invTable(1005,mod);
int n,m;
while(~scanf("%d %d",&n,&m))
{
//cout<<C(m+n,n)<<endl;
//cout<<C(m+n+1,n)<<endl;
//cout<<inv[n+1]<<endl;
printf("%lld\n",1LL*C(m+n,n)*C(m+n+1,n)%mod*inv[n+1]%mod);
}
return 0;
}
B Symmetric Matrix
B题我们很容易想到邻接矩阵,也就是每个点都连有两条边,可以有重边,但不能有自环。
我们设dp[n]为n个点能构成的方案数。
当我们在添加一个点进来时,如果只从中取一个点和它组成环,那么这个点有n-1种取法,剩下的n-2个点合法方案已经求出。
所以方案数为(n-1)*f(n-2)
推广到一般情况,当我们取k个点,剩下的点与新点组成环,旧点有C(n-1,k),剩下的点与新点组成的环的方案数为(n-1-k)!种,但考虑对称性需要除以2,又考虑只取一个点的时候不需要考虑对称性,所以把这种情况单独拿出来。当然也可以取出0个。
那么化简后的公式为:
这里有个讨厌的求和,我们可以将其化为递推式。
设
那么
,再化简即可得到g(n+1)与g(n)的关系
所以
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int f[maxn],g[maxn];
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
if(m==1)
{
puts("0");
continue;
}
f[1]=0;
f[2]=1;
f[3]=1;
long long sum=1;
g[3]=1;
for(int i=4;i<=n;i++)
{
g[i]=(1LL*(i-1)*g[i-1]%m+1LL*(i-1)*(i-2)/2%m*f[i-3]%m)%m;
f[i]=(1LL*(i-1)*f[i-2]%m+g[i])%m;
}
//cout<<g[4]<<endl;
//+------cout<<f[4]<<endl;
printf("%d\n",f[n]);
}
return 0;
}
D Two Graphs
由于n只有8,所以我们可以枚举所有的映射,根据所映射对应的点之间的边集来作为判断依据,这里注意不能用映射对应的点来作为判断依据,因为就算点选的是一样的,也可以选不同的边来称为同构图。
#include<bits/stdc++.h>
using namespace std;
set<vector<int>>S;
vector<int>V[10];
int mp2[10][10];
int bijection[10];
bool vis[10];
int n,m1,m2;
bool check()
{
for(int i=1;i<=n;i++)if(V[i].size())
{
for(int j=0;j<V[i].size();j++)
{
int v=V[i][j];
if(!mp2[bijection[i]][bijection[v]])
return 0;
}
}
return 1;
}
void solve()
{
vector<int>tmp(m2);
for(int i=0;i<m2;i++)tmp[i]=0;
for(int i=1;i<=n;i++)if(V[i].size())
{
for(int j=0;j<V[i].size();j++)
{
int v=V[i][j];
int idx=mp2[bijection[i]][bijection[v]];
tmp[idx-1]=1;
}
}
S.insert(tmp);
}
void dfs(int d)
{
if(d==n+1)
{
if(check())
solve();
return;
}
for(int i=1;i<=n;i++)if(!vis[i])
{
vis[i]=1;
bijection[d]=i;
dfs(d+1);
vis[i]=0;
}
}
int main()
{
while(scanf("%d %d %d",&n,&m1,&m2)==3)
{
memset(mp2,0,sizeof(mp2));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)V[i].clear();
S.clear();
int u,v;
for(int i=1;i<=m1;i++)
{
scanf("%d %d",&u,&v);
V[u].push_back(v);
V[v].push_back(u);
}
for(int i=1;i<=m2;i++)
{
scanf("%d %d",&u,&v);
mp2[u][v]=mp2[v][u]=i;
}
dfs(1);
printf("%d\n",S.size());
}
return 0;
}
E Removal
设dp[i][j] 为到i位置已经删除j个数的不同串个数,在转移时,我们可以枚举下一个数是k,那么就转移到了next[i][k] 这个位置,记忆化搜索就完事了。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int mod=1e9+7;
int nxt[maxn][15];
int now[15];
int dp[maxn][15];
int n,m,k;
int a[maxn];
int dfs(int now,int d)
{
if(d==m)return 1;
if(now==n)
return 0;
if(dp[now][d]!=-1)return dp[now][d];
int res=0;
for(int i=1;i<=k;i++)
{
int nxtidx=nxt[now][i];
if(nxtidx==-1)continue;
int tmp=d+nxtidx-now-1;
if(tmp>m)continue;
res=(res+dfs(nxtidx,tmp))%mod;
}
if(d+n-now==m)res=(res+1)%mod;
return dp[now][d]=res;
}
int main()
{
while(~scanf("%d %d %d",&n,&m,&k))
{
memset(dp,-1,sizeof(dp));
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=k;i++)now[i]=-1;
for(int i=n;i>=0;i--)
{
for(int j=1;j<=k;j++)
nxt[i][j]=now[j];
now[a[i]]=i;
}
dfs(0,0);
printf("%d\n",dp[0][0]);
}
}
F Sum of Maximum
这题想清楚后就会发现很显然,做法也十分套路。
每个数都有自己的最大值,首先我们将a排序,对于a[i-1]!=a[i],我们考虑一下a[i-1]+1到a[i]这个区间的每个数对答案的贡献。
设这个数为x,对于最大值小于x所有的数,它们可以随便取,那么它们提供的方案数为,记为now。
对于最大值不小于x的那些数,他们只能在1到x中取,并且要保证至少有一个数为x。那么我们倒过来想就行了总方案数减一个都没有的方案数。也就是,记为.
所以x对答案的贡献为now*f(x)*x.
那么这段区间对答案的贡献为
.
化简为。
我们设
套路来了。我们观察这个式子,实际上它就是一个幂为n-i+2的多项式,我们想得到这个多项式的某个区间的和,而他的和的函数的最大幂肯定不超过n-i+2,所以我们可以枚举n-i+2个点,计算出n-i+2 个点出来,再利用拉格朗日插值把它的和的函数计算出来。这样就做出来了。
有空研究一下这个模板的奥秘,现在先贴出来吧。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
namespace polysum
{
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
const int D=2010;
ll a[D],f[D],g[D],p[D],p1[D],p2[D],b[D],h[D][2],C[D];
ll powmod(ll a,ll b){ll res=1;a%=mod;assert(b>=0);for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll calcn(int d,ll *a,ll n) { // a[0].. a[d] a[n]
if (n<=d) return a[n];
p1[0]=p2[0]=1;
rep(i,0,d+1) {
ll t=(n-i+mod)%mod;
p1[i+1]=p1[i]*t%mod;
}
rep(i,0,d+1) {
ll t=(n-d+i+mod)%mod;
p2[i+1]=p2[i]*t%mod;
}
ll ans=0;
rep(i,0,d+1) {
ll t=g[i]*g[d-i]%mod*p1[i]%mod*p2[d-i]%mod*a[i]%mod;
if ((d-i)&1) ans=(ans-t+mod)%mod;
else ans=(ans+t)%mod;
}
return ans;
}
void init(int M) {
f[0]=f[1]=g[0]=g[1]=1;
rep(i,2,M+5) f[i]=f[i-1]*i%mod;
g[M+4]=powmod(f[M+4],mod-2);
per(i,1,M+4) g[i]=g[i+1]*(i+1)%mod;
}
ll polysum(ll m,ll *a,ll n) { // a[0].. a[m] \sum_{i=0}^{n-1} a[i]
ll b[D];
for(int i=0;i<=m;i++) b[i]=a[i];
b[m+1]=calcn(m,b,m+1);
rep(i,1,m+2) b[i]=(b[i-1]+b[i])%mod;
return calcn(m+1,b,n-1);
}
ll qpolysum(ll R,ll n,ll *a,ll m) { // a[0].. a[m] \sum_{i=0}^{n-1} a[i]*R^i
if (R==1) return polysum(n,a,m);
a[m+1]=calcn(m,a,m+1);
ll r=powmod(R,mod-2),p3=0,p4=0,c,ans;
h[0][0]=0;h[0][1]=1;
rep(i,1,m+2) {
h[i][0]=(h[i-1][0]+a[i-1])*r%mod;
h[i][1]=h[i-1][1]*r%mod;
}
rep(i,0,m+2) {
ll t=g[i]*g[m+1-i]%mod;
if (i&1) p3=((p3-h[i][0]*t)%mod+mod)%mod,p4=((p4-h[i][1]*t)%mod+mod)%mod;
else p3=(p3+h[i][0]*t)%mod,p4=(p4+h[i][1]*t)%mod;
}
c=powmod(p4,mod-2)*(mod-p3)%mod;
rep(i,0,m+2) h[i][0]=(h[i][0]+h[i][1]*c)%mod;
rep(i,0,m+2) C[i]=h[i][0];
ans=(calcn(m,C,n)*powmod(R,n)-c)%mod;
if (ans<0) ans+=mod;
return ans;
}
}
ll pow2(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int main()
{
int t,n,i,j;
ll ans,a[1111],now,b[1111];
polysum::init(1010);
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
ans=0;
sort(a+1,a+1+n);
a[0]=0;
now=1;
for(int i=1;i<=n;i++)
{
if(a[i]==a[i-1])
{
(now*=a[i])%=mod;
continue;
}
b[0]=0;
for(j=1;j<=n-i+1;j++)
b[j]=1LL*j*(((pow2(j,n-i+1)-pow2(j-1,n-i+1)%mod)+mod)%mod)%mod;
ll tmp=((polysum::polysum(n-i+1,b,a[i]+1)-polysum::polysum(n-i+1,b,a[i-1]+1))%mod+mod)%mod;
(ans+=tmp*now%mod)%=mod;
(now*=a[i])%=mod;
}
printf("%lld\n",ans);
}
return 0;
}
H Longest Path
这题重新让我学习到了斜率dp。
首先我们考虑往下走可以得到的最大值(包括自己连向父亲的边),直接dfs即可得到,记为
up(i)表示该点往上再往下可以得到的最大值,从上往下更新。
它也可以往上走到父亲再往下走到其他的兄弟结点。
那么
重点就是如何快速得到与父亲相同的点中的最大值,看这个式子很容易想到斜率优化,首先我们将父亲是同一个的点放进一个数组里面,按照它们的边排序,再斜率优化从左到右搞一次,从右到左搞一次就行了。
为啥博主搞错了呢,从左到右由于是最大值,那么是维护一个上凸包,并且斜率是递增的(也就是那个常数),所以减的方向实际是tail,不是front。
而从右到左是最小值,那么维护一个下凸包,而斜率是递减的,所以减的方向还是tail。
所以不要想当然的套模板啊。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
vector<pair<int,int> >G[maxn];
typedef pair<int,int> pii;
long long pow2(long long a)
{
return a*a;
}
int c[maxn];
long long down[maxn];
void dfs1(int u,int pre)
{
for(pii x:G[u])if(x.first!=pre)
{
int v=x.first,w=x.second;
c[v]=w;
dfs1(v,u);
if(u!=1)down[u]=max(down[u],down[v]+pow2(c[v]-c[u]));
}
}
long long up[maxn];
int p[maxn];
long long getup(int j,int k)
{
j=p[j],k=p[k];
return down[j]+pow2(1LL*c[j])-down[k]-pow2(c[k]);
}
long long getdown(int j,int k)
{
j=p[j],k=p[k];
return 2LL*(c[j]-c[k]);
}
long long Cal(int idx,int i)
{
if(idx==0)return 0;
idx=p[idx];
i=p[i];
return 1LL*down[idx]+pow2(c[i]-c[idx]);
}
bool cmp1(int a,int b)
{
return c[a]<c[b];
}
int Q[maxn];
void solve(int n)
{
sort(p+1,p+1+n,cmp1);
int front=1,tail=0;
Q[0]=0;
for(int i=1;i<=n;i++)
{
while(front<tail&&getup(Q[tail],Q[tail-1])<getdown(Q[tail],Q[tail-1])*c[p[i]])tail--;
int idx=Q[tail];
up[p[i]]=max(up[p[i]],Cal(idx,i));
while(front<tail&&getup(i,Q[tail])*getdown(Q[tail],Q[tail-1])>getup(Q[tail],Q[tail-1])*getdown(i,Q[tail]))tail--;
Q[++tail]=i;
}
front=1,tail=0;
Q[0]=0;
for(int i=n;i>=1;i--)
{
while(front<tail&&getup(Q[tail],Q[tail-1])<getdown(Q[tail],Q[tail-1])*c[p[i]])tail--;
int idx=Q[tail];
up[p[i]]=max(up[p[i]],Cal(idx,i));
while(front<tail&&getup(i,Q[tail])*getdown(Q[tail],Q[tail-1])<getup(Q[tail],Q[tail-1])*getdown(i,Q[tail]))tail--;
Q[++tail]=i;
}
}
long long ans[maxn];
void dfs2(int u,int pre)
{
int k=0;
if(u!=1&&pre!=1)
up[u]=max(up[u],up[pre]+pow2(c[u]-c[pre]));
for(pii x:G[u])if(x.first!=pre)
{
int v=x.first,w=x.second;
p[++k]=v;
}
solve(k);
ans[u]=up[u];
for(pii x:G[u])if(x.first!=pre)
{
int v=x.first,w=x.second;
ans[u]=max(ans[u],down[v]);
dfs2(v,u);
}
}
int main()
{
int N;
while(~scanf("%d",&N))
{
for(int i=1;i<=N;i++)G[i].clear();
memset(up,0,sizeof(up));
memset(down,0,sizeof(down));
memset(ans,0,sizeof(ans));
int u,v,c;
for(int i=1;i<N;i++)
{
scanf("%d %d %d",&u,&v,&c);
G[u].push_back(make_pair(v,c));
G[v].push_back(make_pair(u,c));
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=N;i++)
printf("%lld\n",ans[i]);
}
return 0;
}
I Substring
求本质不同子串个数。
什么叫本质不同的子串,ab,ac,bc…都是本质相同的子串。我们试想一个串求不同串时,ab,ac都会被算一次,而实际上他们本质是相同的,只是位置上的字符不同罢了。所以我们不妨将abc所有的映射都预处理出来(6个),将原字符串映射成6份,那么ab,ac,bc,ba,ca,cb都会被算进去,再除以6就相当于只算了一次了。
但aa这种只出现一种字符的字符串在这种处理下,只会变成aa,bb,cc三种,所以我们要找到映射后的不同子串个数ans1,和最长的只出现一种字符串的长度ans2,那么答案即为(ans1-3*ans2)/6+ans2.
#include<bits/stdc++.h>
using namespace std;
#define rank rk
const int maxn=50005*6+100;
int ch[maxn];
int cntA[maxn],cntB[maxn],A[maxn],B[maxn],tsa[maxn],SA[maxn],rank[maxn],height[maxn];
int N;
void get_SA()
{
for(int i=0;i<=200;i++)cntA[i]=0;
for(int i=1;i<=N;i++)cntA[ch[i]]++;
for(int i=1;i<=200;i++)cntA[i]+=cntA[i-1];
for(int i=N;i>=1;i--)SA[cntA[ch[i]]--]=i;
rank[SA[1]]=1;
for(int i=2;i<=N;i++)
{
rank[SA[i]]=rank[SA[i-1]];
if(ch[SA[i]]!=ch[SA[i-1]])
rank[SA[i]]++;
}
//cout<<endl;
for(int step=1;rank[SA[N]]<N;step<<=1)
{
for(int i=1;i<=N;i++)cntA[i]=cntB[i]=0;
for(int i=1;i<=N;i++)
{
cntA[A[i]=rank[i]]++;
cntB[B[i]=(i+step<=N)?rank[i+step]:0]++;
}
for(int i=1;i<=N;i++)cntA[i]+=cntA[i-1],cntB[i]+=cntB[i-1];
for(int i=N;i>=1;i--)tsa[cntB[B[i]]--]=i;
for(int i=N;i>=1;i--)SA[cntA[A[tsa[i]]]--]=tsa[i];
rank[SA[1]]=1;
for(int i=2;i<=N;i++)
{
rank[SA[i]]=rank[SA[i-1]];
if(A[SA[i]]!=A[SA[i-1]]||B[SA[i]]!=B[SA[i-1]])
rank[SA[i]]++;
}
}
}
void get_Height()
{
int i,j,k=0;
for(int i=1; i<=N; i++)
{
if(k)k--;
j=SA[rank[i]-1];
while(ch[i+k]==ch[j+k])k++;
height[rank[i]]=k;
}
}
int bij[7][4];
void init()
{
int cas=0;
for(int j=0; j<3; j++)
for(int p=0; p<3; p++)if(p!=j)
for(int q=0; q<3; q++)if(p!=q&&q!=j)
{
cas++;
bij[cas][0]=j;
bij[cas][1]=p;
bij[cas][2]=q;
}
}
char s[50005];
void getch()
{
int len=strlen(s+1);
for(int i=1;i<=6;i++)
{
for(int j=1;j<=len;j++)
ch[++N]=bij[i][s[j]-'a'];
ch[++N]=3;
}
//cout<<endl;
}
int n;
long long get1()
{
int pre=0;
long long ans=0;
//cout<<height[1]<<endl;
for(int i=1;i<=N;i++)
{
ans=ans+n-(SA[i]-1)%(n+1)-min({height[i],pre,n-(SA[i]-1)%(n+1)});
//cout<<SA[i]<<endl;
pre=n-(SA[i]-1)%(n+1);
}
return ans;
}
long long get2()
{
long long res=0;
long long cnt=0;
for(int i=1;i<=n;i++)
{
if(s[i]!=s[i-1])
{
res=max(res,cnt);
cnt=1;
}
else
cnt++;
}
res=max(res,cnt);
return res;
}
int main()
{
init();
while(~scanf("%d",&n))
{
ch[0]=3;
N=0;
scanf("%s",s+1);
getch();
get_SA();
get_Height();
long long ans1=get1();
long long ans2=get2();
//cout<<ans1<<endl;
//cout<<ans2<<endl;
printf("%lld\n",(ans1-ans2*3)/6+ans2);
}
}
J Different Integers
签到题没啥好说的,莫队就完事了。
#include<bits/stdc++.h>
using namespace std;
int len;
const int maxn=1e5+5;
struct Query
{
int L,R,block;
int idx;
Query(int l,int r,int i):L(l),R(r),idx(i)
{
block=L/len;
}
Query(){}
bool operator<(const Query &b)const
{
if(block!=b.block)return block<b.block;
return R<b.R;
}
};
Query q[maxn];
int ans[maxn];
int times[maxn];
int havenum;
int a[maxn];
void insert(int num)
{
times[num]++;
if(times[num]==1)havenum++;
}
void erase(int num)
{
times[num]--;
if(times[num]==0)havenum--;
}
int main()
{
int N,Q;
while(~scanf("%d %d",&N,&Q))
{
havenum=0;
memset(times,0,sizeof(times));
len=(int)sqrt(N);
for(int i=1;i<=N;i++)scanf("%d",&a[i]);
int l,r;
for(int i=1;i<=Q;i++)
{
scanf("%d %d",&l,&r);
q[i]=Query(l,r,i);
}
sort(q+1,q+1+Q);
int L=1,R=1;
for(int i=1;i<=N;i++)
insert(a[i]);
insert(a[1]);
for(int i=1;i<=Q;i++)
{
Query tmp=q[i];
while(R<tmp.R)erase(a[R++]);
while(L>tmp.L)erase(a[L--]);
while(R>tmp.R)insert(a[--R]);
while(L<tmp.L)insert(a[++L]);
ans[tmp.idx]=havenum;
}
for(int i=1;i<=Q;i++)
printf("%d\n",ans[i]);
}
return 0;
}
补难题好爽,也好烦。继续加油吧。
上一篇: deepin 下安装xdroid
推荐阅读
-
2019牛客暑期多校训练营(第二场)H Second Large Rectangle
-
2020牛客暑期多校训练营(第四场)——Basic Gcd Problem
-
2020牛客暑期多校训练营(第五场)
-
2020牛客暑期多校训练营(第九场) The Flee Plan of Groundhog
-
2020牛客暑期多校训练营Groundhog and Apple Tree(树形dp,贪心)
-
2020暑期牛客多校训练营第九场(K)The Flee Plan of Groundhog(lca,树形dp)
-
2020牛客暑期多校训练营(第二场)Cover the Tree
-
2020牛客暑期多校训练营(第一场)H-Minimum-cost Flow
-
2020牛客暑期多校 第一场 F Infinite String Comparision(字符串)
-
Harmony Pairs 2020牛客暑期多校训练营(第六场)