发现自己还有好多题没改/总结,所以弄了这么个东西;
空着的就是还没改完或者是没来得及写题解的。
由于目前还在不断地打新的模拟赛,所以大概会从两头向中心更新(
[0] CSP提高组模拟1
A 最短路
原题:P2966 Cow Toll Paths G
\(n \le 300\),考虑 Floyd。
对点权从小到大排序,然后直接循环转移即可;
此时由于数组从小到大排序,除 \(i\),\(j\) 外没有其它点的点权比中间接口 \(k\) 的点权大,所以这样做是对的。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=305;
int n,m,q;
int dis[N][N];
int ans[N][N];
struct node{
int id,val;
}a[N];
main(){
freopen("path.in","r",stdin);
freopen("path.out","w",stdout);
cin>>n>>m>>q;
for(int i=1;i<=n;i++){
cin>>a[i].val;
a[i].id=i;
}
sort(a+1,a+1+n,[](node a,node b){return a.val<b.val;});
memset(dis,0x3f,sizeof(dis));
memset(ans,0x3f,sizeof(ans));
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
dis[u][v]=dis[v][u]=min(dis[u][v],w);
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
if(i==k)continue;
for(int j=1;j<=n;j++){
if(i==j||k==j)continue;
dis[a[i].id][a[j].id]=min(dis[a[i].id][a[j].id],dis[a[i].id][a[k].id]+dis[a[k].id][a[j].id]);
ans[a[i].id][a[j].id]=min(ans[a[i].id][a[j].id],dis[a[i].id][a[j].id]+max({a[i].val,a[j].val,a[k].val}));
}
}
}
for(int i=1,x,y;i<=q;i++){
cin>>x>>y;
if(ans[x][y]>1e17)cout<<"-1\n";
else cout<<ans[x][y]<<"\n";
}
return 0;
}
我们充分发扬人类智慧。
观察到 Floyd 可能无法一次将答案全部更新,所以我们选择多次迭代使答案收敛至最优解。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=305;
pii a[N][N];
int n,m,v[N],x,y,q,s,t,w;
main(){
freopen("path.in","r",stdin);
freopen("path.out","w",stdout);
cin>>n>>m>>q;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)a[i][j]={0,0};
else a[i][j]={0x3f3f3f3f3f3f3f3f,0};
}
}
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<=m;i++){
cin>>x>>y>>w;
w=min(w,a[x][y].first);
a[x][y].first=a[y][x].first=w;
a[x][y].second=a[y][x].second=max(v[x],v[y]);
}
int T=3;
while(T--)
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
if(i==k)continue;
for(int j=1;j<=n;j++){
if(j==k||i==j)continue;
if(a[i][j].first+a[i][j].second>a[i][k].first+a[k][j].first+max(a[i][k].second,a[k][j].second)){
a[i][j].first=a[i][k].first+a[k][j].first;
a[i][j].second=max(a[i][k].second,a[k][j].second);
}
}
}
}
while(q--){
cin>>x>>y;
if(a[x][y].first==0x3f3f3f3f3f3f3f3f)cout<<"-1\n";
else cout<<a[x][y].first+a[x][y].second<<"\n";
}
return 0;
}
B 方格取数
原题:P3474 [POI2008] KUP-Plot purchase
首先我们把所有格子扫一遍,如果一个格子的权值 \(> 2k\) 就直接排除掉,如果介于 \([k,2k]\) 之间就直接输出;
接下来的矩形中只存在小于 \(k\) 的数。
我们发现,此时原问题等价于寻找一个权值和 \(\ge k\) 的子矩阵;
因为如果我们能找出一个权值和大于 \(2k\) 的矩阵,那么一定有解,因为其一定包括了一个权值和在 \([k,2k]\) 之间的矩阵。
所以我们只需要找到权值和 \(\ge k\) 的极大子矩阵。
当我们找到一个 \([k,2k]\) 的矩阵之后,我们一行一行地把原矩阵切下来;
- 如果切完后原矩阵的权值和 \(\in [k,2k]\),输出原矩阵;
- 如果切下来的那一条的权值和 \(\in [k,2k]\),输出切下来的;
- 如果切下来的权值和 \(\ge 2k\),那么再对新切下来的那一条一格一格删,因为所有数 \(< k\),所以一定不会跳过 \([k,2k]\) 这个区间;
- 否则重复此过程。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=2005;
int n,k,a[N][N],sum[N][N],l[N][N],r[N][N],s[N][N],mp[N][N];
int get(int x,int y,int xx,int yy){
return sum[xx][yy]-sum[x-1][yy]-sum[xx][y-1]+sum[x-1][y-1];
}
main(){
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
if(a[i][j]>=k&&a[i][j]<=k*2){
cout<<i<<" "<<j<<" "<<i<<" "<<j;
return 0;
}
else mp[i][j]=(a[i][j]<k);
sum[i][j]=a[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(mp[i][j])s[i][j]=s[i-1][j]+1;
else s[i][j]=0;
}
}
for(int i=1;i<=n;i++){
stack<int> ss;
for(int j=1;j<=n+1;j++){
while(!ss.empty()&&s[i][j]<s[i][ss.top()]){
r[i][ss.top()]=j-1;
ss.pop();
}
ss.push(j);
}
while(!ss.empty())ss.pop();
for(int j=n;j>=0;j--){
while(!ss.empty()&&s[i][j]<s[i][ss.top()]){
l[i][ss.top()]=j+1;
ss.pop();
}
ss.push(j);
}
for(int j=1;j<=n;j++){
int now=get(i-s[i][j]+1,l[i][j],i,r[i][j]);
if(now>=k){
if(now<=k*2){
cout<<i-s[i][j]+1<<" "<<l[i][j]<<" "<<i<<" "<<r[i][j];
return 0;
}
else{
for(int h=l[i][j]+1;h<=r[i][j];h++){
int noww=sum[i][h]-sum[i][l[i][j]-1];
if(noww>=k&&noww<=2*k){
cout<<i<<" "<<l[i][j]<<" "<<i<<" "<<h;
return 0;
}
}
}
}
}
}
cout<<-1;
return 0;
}
C 数组
原题:CF1114F Please, another Queries on Array?
对于欧拉函数,有式子 \(\varphi(x)=x \ \cdot \ \prod_{i=1}^n\limits (\frac{p_i-1}{p_i})\),其中 \(p_i\) 为 \(x\) 的所有质因数。
区间积可以用线段树维护,难点在于质因子的维护。
简单筛一下发现 \(300\) 以内只有 \(62\) 个质数,所以可以简单地用一个 long long
来状压,同样丢到线段树里处理即可。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int mod=1e9+7;
const int N=4e5+5;
int n,a[N],l,r,x,q;
string op;
int prim[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293};
inline int qp(int a,int b,int mod=mod){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
inline int getp(int x){
int p=0;
for(int i=0;i<62;i++){
if(x%prim[i]==0)p|=(1ll<<i);
}
return p;
}
struct tree{
int l,r,mul,lzm;
int p,lzp;
int len(){return r-l+1;}
}t[N<<2];
void pushup(int k){
t[k].mul=t[k<<1].mul*t[k<<1|1].mul%mod;
t[k].p=t[k<<1].p|t[k<<1|1].p;
}
void build(int k,int l,int r){
t[k]={l,r,a[l],1};
if(l==r){
int p=getp(a[l]);
t[k].p=p;t[k].lzp=0;
return;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(k);
}
void pushdown(int k){
if(t[k].lzm!=1){
t[k<<1].mul=t[k<<1].mul*qp(t[k].lzm,t[k<<1].len())%mod;
t[k<<1].lzm=t[k<<1].lzm*t[k].lzm%mod;
t[k<<1|1].mul=t[k<<1|1].mul*qp(t[k].lzm,t[k<<1|1].len())%mod;
t[k<<1|1].lzm=t[k<<1|1].lzm*t[k].lzm%mod;
t[k].lzm=1;
}
if(t[k].lzp!=0){
t[k<<1].p|=t[k].lzp;
t[k<<1].lzp|=t[k].lzp;
t[k<<1|1].p|=t[k].lzp;
t[k<<1|1].lzp|=t[k].lzp;
t[k].lzp=0;
}
}
void update(int k,int l,int r,int val,int p){
if(l<=t[k].l&&t[k].r<=r){
t[k].mul=t[k].mul*qp(val,t[k].len())%mod;
t[k].lzm=t[k].lzm*val%mod;
t[k].p|=p;
t[k].lzp|=p;
return;
}
pushdown(k);
int mid=(t[k].l+t[k].r)>>1;
if(l<=mid)update(k<<1,l,r,val,p);
if(r>mid)update(k<<1|1,l,r,val,p);
pushup(k);
}
pii query(int k,int l,int r){
if(l<=t[k].l&&t[k].r<=r){
return {t[k].mul,t[k].p};
}
pushdown(k);
int mid=(t[k].l+t[k].r)>>1;
pii ans={1,0};
if(l<=mid){
pii L=query(k<<1,l,r);
ans.first=ans.first*L.first%mod;
ans.second|=L.second;
}
if(r>mid){
pii R=query(k<<1|1,l,r);
ans.first=ans.first*R.first%mod;
ans.second|=R.second;
}
return ans;
}
int inv[500];
main(){
freopen("array.in","r",stdin);
freopen("array.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
for(int i=1;i<=400;i++)inv[i]=qp(i,mod-2);
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n);
while(q--){
cin>>op;
if(op=="1"){
cin>>l>>r>>x;
int p=getp(x);
update(1,l,r,x,p);
}
else{
cin>>l>>r;
pii tmp=query(1,l,r);
int ans=tmp.first,p=tmp.second;
for(int i=0;i<62;i++){
if((p>>i)&1){
ans=ans*(prim[i]-1)%mod*inv[prim[i]]%mod;
}
}
cout<<ans<<"\n";
}
}
return 0;
}
D 树
根号分治。
设块长为 \(T\)。
预处理出步长为 \([1,\sqrt T]\) 时每个点向上跳到根的点权和以及每个点的 \(1 \backsim \sqrt T\)
大于 \(T\) 则暴力跳,否则树上差分。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+5;
int n,b[N],c[N];
int sum[N][250],up[N][250],fa[N][20],dep[N],sq;
struct edge{
int next,to;
}e[N*2];
int h[N],cnt;
void add(int u,int v){
e[++cnt]={h[u],v};
h[u]=cnt;
}
void dfs(int x){
dep[x]=dep[fa[x][0]]+1;
for(int i=1;i<16;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
up[x][0]=x;
up[x][1]=fa[x][0];
sum[x][1]=sum[x][0]+sum[fa[x][0]][1];
for(int i=2;i<=sq;i++){
up[x][i]=up[fa[x][0]][i-1];
sum[x][i]=sum[x][0]+sum[up[x][i]][i];
}
for(int i=h[x];i;i=e[i].next){
int to=e[i].to;
if(to==fa[x][0])continue;
fa[to][0]=x;
dfs(to);
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=19;i>=0;i--){
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
}
if(x==y)return x;
for(int i=19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int getans(int u,int v,int dis){
int l=lca(u,v),ans=0;
if(dis<=sq){
if(l==u)return sum[v][dis]-sum[up[u][dis]][dis];
if(l==v)return sum[u][dis]-sum[up[v][dis]][dis];
ans=sum[v][dis]+sum[u][dis];
int t=fa[l][0];
while(t&&dep[t]%dis!=dep[u]%dis)t=fa[t][0];
ans-=sum[t][dis];
t=l;
while(t&&dep[t]%dis!=dep[v]%dis)t=fa[t][0];
ans-=sum[t][dis];
return ans;
}
ans=sum[u][0];
while(dep[u]>dep[l]){
int w=dis;
while(w>sq){
u=up[u][sq];
w-=sq;
}
u=up[u][w];
if(dep[u]>=dep[l])ans+=sum[u][0];
}
if(u==v)return ans;
ans+=sum[v][0];
while(dep[v]>dep[l]){
int w=dis;
while(w>sq){
v=up[v][sq];
w-=sq;
}
v=up[v][w];
if(dep[v]>dep[l])ans+=sum[v][0];
}
return ans;
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
sq=sqrt(n);
for(int i=1;i<=n;i++)cin>>sum[i][0];
for(int i=1,x,y;i<n;i++){
cin>>x>>y;
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=1;i<n;i++)cin>>c[i];
dfs(1);
for(int i=1;i<n;i++){
cout<<getans(b[i],b[i+1],c[i])<<"\n";
}
return 0;
}
[1] 暑假集训CSP提高模拟1
A Start
大模拟。
- 注意判负。
- 记得清除 \(\texttt{double}\) 标记。
- 除法是向下取整而非向零取整。
- 每轮新开始时重置方向。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=INT_MAX;
string nm[10]={"","D","B","A","C","E","PASS","TURN","DOUBLE"};
string nmm[10]={"","C","A","B","D","E","PASS","TURN","DOUBLE"};
struct card{
string name;
int num;
void input(){
string s;
cin>>s;
if(s!="PASS"&&s!="TURN"&&s!="DOUBLE"){
name=s[0];
int x=0;
for(int i=1;i<(int)s.size();i++){
x=(x<<3)+(x<<1)+(s[i]^48);
}
num=x;
}
else name=s,num=0;
}
};
struct player{
string name;
card c[5];
int nxt,pre;
}a[10];
int n,m,k,p,now,minn,miid;
queue<card> q;
card tmp;
bool db;
int playbig(int x,int p){
int maxn=-inf,id=0;
for(int i=1;i<=5;i++){
for(int j=1;j<=3;j++){
tmp=a[x].c[j];
if(tmp.name==nmm[i]){
int to=p;
if(i==1)to*=tmp.num;
else if(i==2)to+=tmp.num;
else if(i==3)to-=tmp.num;
else if(i==4)to>>=1;
else if(i==5)to=tmp.num;
if(to>99)continue;
if(to>maxn){
maxn=to;
id=j;
}
}
}
}
if(id){
tmp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[id].name<<a[x].c[id].num<<",now p="<<maxn<<".\n";
a[x].c[id]=tmp;
return maxn;
}
for(int i=6;i<=8;i++){
for(int j=1;j<=3;j++){
tmp=a[x].c[j];
if(tmp.name==nmm[i]){
if(i==6){
card tmpp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[j].name<<",now p="<<p<<".\n";
a[x].c[j]=tmpp;
return p+1e6;
}
else if(i==7){
card tmpp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[j].name<<",now p="<<p<<".\n";
a[x].c[j]=tmpp;
for(int k=1;k<=n;k++){
swap(a[k].nxt,a[k].pre);
}
return p+1e6;
}
else if(i==8){
card tmpp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[j].name<<",now p="<<p<<".\n";
a[x].c[j]=tmpp;
db=1;
return p+1e6;
}
}
}
}
cout<<a[x].name<<" lost the game.\n";
return 100;
}
int playsmall(int x,int p){
int minn=inf,id=0;
for(int i=6;i<=8;i++){
for(int j=1;j<=3;j++){
tmp=a[x].c[j];
if(tmp.name==nm[i]){
if(i==6){
card tmpp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[j].name<<",now p="<<p<<".\n";
a[x].c[j]=tmpp;
return p+1e6;
}
else if(i==7){
card tmpp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[j].name<<",now p="<<p<<".\n";
a[x].c[j]=tmpp;
for(int k=1;k<=n;k++){
swap(a[k].nxt,a[k].pre);
}
return p+1e6;
}
else if(i==8){
card tmpp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[j].name<<",now p="<<p<<".\n";
a[x].c[j]=tmpp;
db=1;
return p+1e6;
}
}
}
}
for(int i=1;i<=5;i++){
for(int j=1;j<=3;j++){
tmp=a[x].c[j];
if(tmp.name==nm[i]){
int to=p;
if(i==1)to>>=1;
else if(i==2)to-=tmp.num;
else if(i==3)to+=tmp.num;
else if(i==4)to*=tmp.num;
else if(i==5)to=tmp.num;
if(to>99)continue;
if(to<minn){
minn=to;
id=j;
}
}
}
}
if(id){
tmp=q.front();
q.pop();
cout<<a[x].name<<" used "<<a[x].c[id].name<<a[x].c[id].num<<",now p="<<minn<<".\n";
a[x].c[id]=tmp;
return minn;
}
cout<<a[x].name<<" lost the game.\n";
return 100;
}
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>a[i].name;
for(int j=1;j<=3;j++){
a[i].c[j].input();
}
a[i].nxt=i==n?1:i+1;
a[i].pre=i==1?n:i-1;
}
while(k--){
tmp.input();
q.push(tmp);
}
now=1;
for(int t=1;t<=m;t++){
cout<<"Round "<<t<<":\n";
for(int i=1;i<=n;i++){
a[i].nxt=i==n?1:i+1;
a[i].pre=i==1?n:i-1;
}
p=db=0;
while(1){
if(db){
p=playsmall(now,p);
if(p>=1e4){
p=p-1e6;
now=a[now].nxt;
continue;
}
if(p==100)break;
db=0;
}
p=playbig(now,p);
if(p>=1e4)p=p-1e6;
if(p==100){
break;
}
now=a[now].nxt;
}
for(int i=1;i<=3;i++){
tmp=q.front();
q.pop();
a[now].c[i]=tmp;
}
}
return 0;
}
B mine
DP。
设 \(f_{i,0/1/2}\) 分别表示第 \(i+1\) 格不为雷,第 \(i+1\) 格为雷,第 \(i\) 格为雷的方案数,那么对于每个字符:
- 如果该位为
0
,有 \(f_{i,0} \gets f_{i,0}+f_{i-1,0}\); - 如果该位为
1
,有 \(f_{i,0} \gets f_{i,0}+f_{i-1,2} \ ,f_{i,1} \gets f_{i,1}+f_{i-1,0}\); - 如果该位为
2
,有 \(f_{i,1} \gets f_{i,1}+f_{i-1,2}\); - 如果该位为
*
,有 \(f_{i,2} \gets f_{i,2}+f_{i-1,1}+f_{i-1,2}\); - 如果该位为
?
,上述所有转移都要做一遍。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
const int mod=1e9+7;
int n;
ll f[N][3];
char s[N];
int main(){
cin>>(s+1);
n=strlen(s+1);
f[0][0]=f[0][1]=1;
for(int i=1;i<=n;i++){
if(s[i]=='0'||s[i]=='?')f[i][0]+=f[i-1][0];
if(s[i]=='1'||s[i]=='?')f[i][0]+=f[i-1][2],f[i][1]+=f[i-1][0];
if(s[i]=='2'||s[i]=='?')f[i][1]+=f[i-1][2];
if(s[i]=='*'||s[i]=='?')f[i][2]+=f[i-1][1]+f[i-1][2];
f[i][0]%=mod;
f[i][1]%=mod;
f[i][2]%=mod;
}
cout<<(f[n][0]+f[n][2])%mod;
return 0;
}
观察到唐氏作者场上打了又臭又长的记搜,放一下。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
const int mod=1e9+7;
string s;
int n;
int f[N][5][5];
int dfs(int x,int lst,int llst){
if(x==n+1){
int ans=1;
if(lst==1&&llst!=3)ans=0;
else if(lst==2)ans=0;
else if(lst==0&&llst==3)ans=0;
return f[x][lst][llst]=ans;
}
if(f[x][lst][llst]!=-1)return f[x][lst][llst];
if(x==0){
int now;
if(x==0)now=4;
else now=(s[x-1]=='*'?3:s[x-1]-'0');
int ans;
if(now==0&&lst==3)ans=0;
else if(now==1&&x==n&&lst!=3)ans=0;
else if(now==2&&lst!=3)ans=0;
else if(now==3&&lst==1&&llst==3)ans=0;
else if(now==3&&lst==0)ans=0;
else if(now!=3&&lst==1&&llst!=3)ans=0;
else if((now!=3||llst!=3)&&lst==2)ans=0;
else ans=dfs(x+1,now,lst);
return f[x][lst][llst]=ans;
}
else if(s[x-1]!='?'){
int now;
if(x==0)now=4;
else now=(s[x-1]=='*'?3:s[x-1]-'0');
int ans;
if(now==0&&lst==3)ans=0;
else if(now==1&&x==n&&lst!=3)ans=0;
else if(now==2&&lst!=3)ans=0;
else if(now==3&&lst==1&&llst==3)ans=0;
else if(now==3&&lst==0)ans=0;
else if(now!=3&&lst==1&&llst!=3)ans=0;
else if((now!=3||llst!=3)&&lst==2)ans=0;
else ans=dfs(x+1,now,lst);
return f[x][lst][llst]=ans;
}
int ans=0;
if(lst==3){
for(int i=1;i<=3;i++)ans=(ans+dfs(x+1,i,lst))%mod;
}
else if(lst==0){
ans=(dfs(x+1,1,lst)+dfs(x+1,0,lst))%mod;
}
else if(lst==1){
if(llst==3)ans=(dfs(x+1,0,lst)+dfs(x+1,1,lst))%mod;
else ans=dfs(x+1,3,lst);
}
else if(lst==2){
ans=dfs(x+1,3,lst);
}
else ans=(dfs(x+1,0,lst)+dfs(x+1,1,lst)+dfs(x+1,3,lst))%mod;
return f[x][lst][llst]=ans%mod;
}
int main(){
memset(f,-1,sizeof(f));
cin>>s;
n=s.size();
cout<<dfs(0,0,0);
return 0;
}
C 小凯的疑惑
原题:P3951 小凯的疑惑 / [蓝桥杯 2013 省] 买不到的数目
(其实不是原题,不过很相似,算是个弱化版)
数学题。
首先如果 \(\gcd(x,y) \neq 1\),那么答案显然为 \(-1\),因为 \(ax+by\) 只能为 \(\gcd(x,y)\) 的倍数;
所以我们只需考虑 \(\gcd(x,y) = 1\) 的情况。
首先,如果 \(a_i(i \in [1,n])\) 构成模 \(n\) 的完系,那么对于 \(k,m \in \mathbb{Z},\gcd(m,n)=1\),则 \(k+ma_i(i \in [1,n])\) 也构成模 \(n\) 的完系;
所以我们直接考虑模 \(b\) 为 \(1\) 到 \(b-1\) 的最小值在什么时候出现,答案为
\[\sum_{i=0}^{y-1} \lfloor \frac{ix}{y} \rfloor \]由于没有多测,推到这里已经可以过了;
考虑如何化简。
设答案为 \(ans\);
而由于 \(\gcd(x,y)\) 等于 \(1\),所以 \((ix \operatorname{mod} y)\) 会取遍 \(0 \sim y-1\);
所以:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x,y;
main(){
cin>>x>>y;
if(x>y)swap(x,y);
if(x==1||y==1)return cout<<0,0;
if(__gcd(x,y)!=1)return cout<<-1,0;
cout<<(x-1)*(y-1)/2;
return 0;
}
D 春节十二响
由于选出的数不能存在祖先关系,所以到根的每一条路径上最多选择一个点。
考虑贪心。
我们吧最大的一堆放在一起,其次是次大的,以此类推;
DFS 一遍,自底向上合并即可。
直接合并是 \(O(n^2)\) 的,用启发式合并可以降到 \(O(n \log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,a[N],fa[N];
priority_queue<int> q[N];
struct edge{
int next,to;
}e[N*2];
int h[N],cnt;
void add(int u,int v){
e[++cnt]={h[u],v};
h[u]=cnt;
}
void merge(int x,int y){
if(q[x].size()>q[y].size())swap(q[x],q[y]);
while(!q[x].empty()){
int a=q[x].top(),b=q[y].top();
q[x].pop();q[y].pop();
q[0].push(max(a,b));
}
while(!q[0].empty()){
q[y].push(q[0].top());
q[0].pop();
}
}
void dfs(int x){
for(int i=h[x];i;i=e[i].next){
int to=e[i].to;
dfs(to);
merge(to,x);
}
q[x].push(a[x]);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=2;i<=n;i++){
cin>>fa[i];
add(fa[i],i);
}
dfs(1);
ll ans=0;
while(!q[1].empty()){
ans+=q[1].top();
q[1].pop();
}
cout<<ans;
return 0;
}
[2] 暑假集训CSP提高模拟2
A 活动投票
原题:P2397 yyy loves Maths VI (mode)
摩尔投票板子。
设 \(now\) 为现在的众数,\(cnt\) 为当前众数的出现个数
将原数组扫一遍:
- 若 \(cnt=0\),\(now \gets a_i\);
- 若 \(a_i=now\),\(cnt \gets cnt+1\);
- 若 \(a_i \neq now\),\(cnt \gets cnt-1\);
最后输出 \(now\) 即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,x,now,cnt;
int main(){
ios::sync_with_stdio(0);
cin>>n;
while(n--){
cin>>x;
if(!cnt)now=x;
if(x==now)cnt++;
else cnt--;
}
cout<<now;
return 0;
}
B 序列
原题:UVA1608 Non-boring sequences
直接暴力枚举子串暴力判是 \(O(n^4)\) 的,直接爆炸;
并且“枚举子串”的 \(O(n^2)\) 无法进一步优化。
所以转换思路,考虑由每个点入手。
设当前点为 \(x\),我们处理出上一个和下一个 \(a_x\) 出现的位置,这样我们就可以判断在 \([l,r]\) 中该数是否只出现一次;
对于每一个区间,我们找到一个 \(x\) 使 \(a_x\) 只在该区间内出现过一次;
此时跨过位置 \(x\) 的区间均合法,所以我们可以直接向左右递归;
如果我们直接找 \(x\),复杂度是 \(O(n^2)\) 的,所以我们可以采用中途相遇法,从左右两端同时找,复杂度降至 \(O(n \log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int T,n;
int a[N],pre[N],nxt[N];
map<int,int> mp;
bool solve(int L,int R){
if(L>=R)return 1;
int l=L,r=R;
while(l<=r){
if(pre[l]<L&&nxt[l]>R)return solve(L,l-1)&&solve(l+1,R);
if(pre[r]<L&&nxt[r]>R)return solve(L,r-1)&&solve(r+1,R);
l++;r--;
}
return 0;
}
int main(){
cin>>T;
while(T--){
mp.clear();
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
nxt[i]=n+1;
pre[i]=mp[a[i]],nxt[pre[i]]=i;
mp[a[i]]=i;
}
cout<<(solve(1,n)?"non-boring":"boring")<<"\n";
}
return 0;
}
C Legacy
线段树优化建图。
操作中有点对点连边,点对区间连边和区间对点连边三种。
暴力建图会时空双爆,所以我们采用线段树来优化;
开两棵线段树,一棵记录入边,一棵记录出边,相同区间之间连边权为 \(0\) 的边;
点对点正常连,点对区间和区间对点只需要暴力找出对应区间连边就行。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=3e6+5;
const int K=5e5;
int n,m,s,op,x,y,z,l,r,w,a[N],dis[N],vis[N];
struct edge{
int next,to,w;
}e[N];
int h[N],cnt;
void add(int u,int v,int w){
e[++cnt]={h[u],v,w};
h[u]=cnt;
}
void build(int p,int l,int r){
if(l==r){
a[l]=p;
return;
}
int mid=(l+r)>>1;
add(p,p<<1,0);add(p,p<<1|1,0);
add((p<<1)+K,p+K,0);add((p<<1|1)+K,p+K,0);
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
void update(int p,int l,int r,int L,int R,int v,int w,int op){
if(L<=l&&r<=R){
if(op==2)add(v+K,p,w);
else add(p+K,v,w);
return;
}
int mid=(l+r)>>1;
if(L<=mid)update(p<<1,l,mid,L,R,v,w,op);
if(R>mid)update(p<<1|1,mid+1,r,L,R,v,w,op);
}
void dij(int s){
priority_queue<pii> q;
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push({0,s});
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=h[u];i;i=e[i].next){
int to=e[i].to;
if(dis[to]>dis[u]+e[i].w){
dis[to]=dis[u]+e[i].w;
q.push({-dis[to],to});
}
}
}
}
main(){
cin>>n>>m>>s;
build(1,1,n);
for(int i=1;i<=n;i++){
add(a[i],a[i]+K,0);
add(a[i]+K,a[i],0);
}
for(int i=1;i<=m;i++){
cin>>op;
if(op==1){
cin>>x>>y>>z;
add(a[x]+K,a[y],z);
}
else{
cin>>x>>l>>r>>w;
update(1,1,n,l,r,a[x],w,op);
}
}
dij(a[s]+K);
for(int i=1;i<=n;i++){
if(dis[a[i]]==0x3f3f3f3f3f3f3f3f)dis[a[i]]=-1;
cout<<dis[a[i]]<<" ";
}
return 0;
}
D DP搬运工1
预设型 DP。
设 \(f_{i,j,k}\) 表示考虑到 \(i\),填了 \(j+1\) 段,相邻两数最大值之和为 \(k\) 的方案数;
初值为 \(f_{1,0,0}=1\);
考虑如何转移。
- 如果新数插在第一段的左边或最后一段右边,那么:
- 如果该数与其它数相邻,那么段数不变,和增加 \(i\),有转移 \(f_{i,j,k+i} \gets f_{i,j,k+i}+2 \times f_{i-1,j,k}\);
- 如果该数与其它数不相邻,则段数加 \(1\),和不变,有转移 \(f_{i,j+1,k} \gets f_{i,j+1,k}+2 \times f_{i-1,j,k}\);
- 如果新数插在某两端中间,那么:
- 如果该数与左右均相邻,那么段数减 \(1\),和增加 \(2i\),有转移 \(f_{i,j-1,k+2i} \gets f_{i,j-1,k+2i} + j \times f_{i-1,j,k}\);
- 如果该数只与左右一段相邻,那么段数不变,和增加 \(i\),有转移 \(f_{i,j,k+i} \gets f_{i,j,k+i}+2j\times f_{i-1,j,k}\);
- 如果该数与左右都不相邻,那么段数加 \(1\),和不变,有转移 \(f_{i,j+1,k} \gets f_{i,j,k+1}+j\times f_{i-1,j,k}\);
最终 \(\sum_{i=0}^m \limits f_{n,0,i}\) 即为所求。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=55;
const int mod=998244353;
int f[N][N][N*N];
int n,K;
main(){
cin>>n>>K;
f[1][0][0]=1;
for(int i=2;i<=n;i++){
for(int j=0;j<=n-i+1;j++){
for(int k=0;k<=K;k++){
if(!f[i-1][j][k])continue;
if(j){
f[i][j][k+i]=(f[i][j][k+i]+f[i-1][j][k]*2%mod*j%mod)%mod;
f[i][j+1][k]=(f[i][j+1][k]+f[i-1][j][k]*j%mod)%mod;
f[i][j-1][k+2*i]=(f[i][j-1][k+2*i]+f[i-1][j][k]*j%mod)%mod;
}
f[i][j][k+i]=(f[i][j][k+i]+f[i-1][j][k]*2%mod)%mod;
f[i][j+1][k]=(f[i][j+1][k]+f[i-1][j][k]*2%mod)%mod;
}
}
}
int ans=0;
for(int i=0;i<=K;i++){
ans=(ans+f[n][0][i])%mod;
}
cout<<ans;
return 0;
}
E DP搬运工2
预设型 DP。
设 \(f_{i,j}\) 表示考虑到 \(i\) 时有 \(j\) 个满足条件的数,此时的方案数;
初值为 \(f_{1,0}=f_{2,0}=1\);
考虑如何转移。
- 如果新数插在一段的中间,符合条件的数加了一组减了一组,不变,有转移 \(f_{i,j} \gets f_{i,j}+f_{i-1,j}\);
- 如果新数插在一段两边,符合条件的数不变,转移仍为 \(f_{i,j} \gets f_{i,j}+f_{i-1,j}\);
- 其余情况下符合条件的数均会增加 \(1\),有转移 \(f_{i,j+1} \gets f_{i,j+1}+f_{i-1,j}\);
最终 \(f_{n,k}\) 即为所求。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=2005;
const int mod=998244353;
int f[N][N];
int n,k;
main(){
cin>>n>>k;
f[1][0]=1;f[2][0]=2;
for(int i=3;i<=n;i++){
int mk=min(k,i/2);
for(int j=0;j<=mk;j++){
if(!f[i-1][j])continue;
f[i][j]=(f[i][j]+f[i-1][j]*((j+1)*2))%mod;
f[i][j+1]=(f[i][j+1]+f[i-1][j]*(i-((j+1)*2)))%mod;
}
}
cout<<f[n][k];
return 0;
}
F DP搬运工3
预设型 DP。
有两重排列,不好做,考虑先固定住 B 序列为 \(1,2,3,...,n\),最后乘上一个 \(n!\) 即可。
设 \(f_{i,j,k}\) 表示考虑到第 \(i\) 个数,其中有 \(j\) 个填到了 \(i+1 \sim n\) 的位置,所有小于 \(i\) 的 \(\max(a_x,b_x)\) 的和为 \(k\) 时的方案数;
初值为 \(f_{0,0,0}=0\)。
考虑如何转移。设新插入的数为 \(i\)。
- 如果 \(i\) 插入到了第 \(i\) 个位置上,则
k+=i
,有转移 \(f_{i,j,k+i} \gets f_{i,j,k+i}+f_{i-1,j,k}\); - 如果 \(i\) 插入到了大于 \(i\) 的位置上:
- 如果 \(i\) 位置插入了大于 \(i\) 的数,则
j++
,有转移 \(f_{i,j+1,k} \gets f_{i,j+1,k}+f_{i-1,j,k}\); - 如果 \(i\) 位置插入了小于 \(i\) 的数,则
k+=i
,有转移 \(f_{i,j,k+i} \gets f_{i,j,k+i}+f_{i-1,j,k}\);
- 如果 \(i\) 位置插入了大于 \(i\) 的数,则
- 如果 \(i\) 插入到了小于 \(i\) 的位置上:
- 如果 \(i\) 位置插入了小于 \(i\) 的数,则
j--,k+=2*i
,有转移 \(f_{i,j-1,k+2i} \gets f_{i,j-1,k+2i}+f_{i-1,j,k}\); - 如果 \(i\) 位置插入了大于 \(i\) 的数,则
k+=i
,有转移 \(f_{i,j,k+i} \gets f_{i,j,k+i}+f_{i-1,j,k}\);
- 如果 \(i\) 位置插入了小于 \(i\) 的数,则
最终 \(\sum_{x \ge K} \limits f_{n,0,x}\) 即为所求。可以在转移时直接将大于 \(K\) 的加到 \(K\) 里。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=55;
const int mod=998244353;
int n,K;
int f[N][N][N*N];
main(){
cin>>n>>K;
f[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<=K;k++){
f[i][j][min(k+i,K)]=(f[i][j][min(k+i,K)]+(2*j+1)*f[i-1][j][k]%mod)%mod;
f[i][j+1][k]=(f[i][j+1][k]+f[i-1][j][k]);
if(j)f[i][j-1][min(k+2*i,K)]=(f[i][j-1][min(k+2*i,K)]+j*j*f[i-1][j][k]%mod)%mod;
}
}
}
int ans=f[n][0][K];
for(int i=1;i<=n;i++)ans=ans*i%mod;
cout<<ans;
return 0;
}
[3] 暑假集训CSP提高模拟3
A abc猜想
B 简单的排列最优化题
C 简单的线性做法题
D 简单的线段树题
[4] 暑假集训CSP提高模拟4
A White and Black
B White and White
C Black and Black
D Black and White
[SP1] 暑假集训 加赛1
A 修仙(restart)
B 七负我
[5] 暑假集训CSP提高模拟5
A 简单的序列(sequence)
B 简单的字符串(string)
C 简单的博弈(tree)
D 困难的图论(graph)
[6] 暑假集训CSP提高模拟6
A 花间叔祖
B 合并r
C 回收波特
D 斗篷
[SP2] 加赛2
A AA
B AB
C AC
D AD
[7] 暑假集训CSP提高模拟7
A Permutations & Primes
B 树上游戏
C Ball Collector
D 满穗
[8] 暑假集训CSP提高模拟8
A 基础的生成函数练习题(gf)
B 简单的拉格朗日反演练习题(lagrange)
C 容易的多元拉格朗日反演练习题(multi)
D 朴素的抽象代数题(algebra)
[9] 暑假集训CSP提高模拟9
A 大众点评
B 录取查询
C 精准打击
D 你画我猜
[10] 暑假集训CSP提高模拟10
A 黑暗型高松灯
B 速度型高松灯
C 力量型高松灯
D 高松灯
[11] 暑假集训CSP提高模拟11
A Fate
B EVA
C 嘉然登场
D Clannad
[12] 暑假集训CSP提高模拟12
A 黑客
B 密码技术
C 修水管
D 货物搬运
[13] 暑假集训CSP提高模拟13
A 小孩召开法 1
B 小孩召开法 2
C 小孩召开法 3
D 小孩召开法 4
[SP3] 欢欢乐乐赛赛
A 树构造
B 长途巴士
C 你是黄金奖杯
D 地主斗
E Grouping
F Pivot
G 11 : 23
H 烙印融合
I 魔术刻印
J persona
K 可持久化非确定性有穷状态决策自动机
L 随
[14] 暑假集训CSP提高模拟14
A BA
B BB
C BC
D BD
[15] 暑假集训CSP提高模拟15
A 串串
B 排排
C 序序
D 桥桥
[16] 暑假集训CSP提高模拟16
A 九次九日九重色
B 天色天歌天籁音
C 春色春恋春熙风
D 雪色雪花雪余痕
[17] 暑假集训CSP提高模拟17
A 符号化方法初探
B 无标号 Sequence 构造
C 无标号 Multiset 构造
D 有限制的构造
[18] 暑假集训CSP提高模拟18
A Mortis
B 生活在hzoi上
C 嘉然今天吃什么
D APJifengc
[19] 暑假集训CSP提高模拟19
A 数字三角形
B 那一天她离我而去
C 哪一天她能重回我身边
D 单调区间
[20] 暑假集训CSP提高模拟20
A Kanon
B Summer Pockets
C 空之境界
D 穗
[21] 暑假集训CSP提高模拟21
A 黎明与萤火
B Darling Dance
C Non-breath oblige
D 妄想感伤代偿联盟
[22] 暑假集训CSP提高模拟22
A 法阵
B 连通块
C 军队
D 棋盘
[23] 暑假集训CSP提高模拟23
A 进击的巨人
B Wallpaper Collection
C 樱花庄的宠物女孩
D 机动车驾驶员考试
[24] 暑假集训CSP提高模拟24
A 与和
B 函数
C 袋鼠
D 最短路
[25] 暑假集训CSP提高模拟25
A 可持久化线段树
B Little Busters !
C 魔卡少女樱
D 声之形
[26] 暑假集训CSP提高模拟26
A 博弈
B 跳跃
C 大陆
D 排列
[27] 暑假集训CSP提高模拟27
A 线性只因
B 金箱子
C 可持久化字符串
D 圣诞树
[28] csp-s模拟1
A 喜剧的迷人之处在于
B 镜中的野兽
C 我愿相信由你所描述的童话
D Baby Doll
[29] csp-s模拟2
A 不相邻集合
B 线段树
C 魔法师
D 园艺
[30] csp-s加赛1
A 小W与伙伴招募
B 小W与制胡串谜题
C 小W与屠龙游戏
[31] csp-s加赛2
A string
B subset
C rook
[31] csp-s模拟3
A 奇观
B 铁路
C 光纤
D 权值
[32] csp-s模拟4
A 商品
B 价值
C 货币
D 资本
[33] 冲刺CSP联训模拟1[衡中]
A 几何
考虑 DP。
设 \(f_{i,j,k}\) 表示在主串上匹配到第 \(i\) 位,\(x\) 在经过若干次循环后匹配到第 \(j\) 位,\(y\) 匹配到第 \(k\) 位;
有转移: $$f_{i,j,k} \gets f_{i-1,j-1,k} \ \ \ if \ \ s_{i-1}=x_{j-1}$$
朴素转移是 \(O(|s||x||y|)\) 的,考虑优化。
观察到 \(O(|s||x||y|)\) 在 \(1e9\) 左右且常数很小,并且状态只有 \(0\) 和 \(1\),考虑使用 bitset
进行优化,复杂度 \(O(\frac{|s||x||y|}{w})\),可以通过。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int T;
char s[N],x[55],y[55];
bitset<55> f[55],g[30];
int main(){
freopen("geometry.in","r",stdin);
freopen("geometry.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>(s+1)>>(x+1)>>(y+1);
int ls=strlen(s+1),lx=strlen(x+1),ly=strlen(y+1);
f[0][0]=f[lx][0]=f[0][ly]=f[lx][ly]=1;
for(int i=1;i<=ly;i++)g[y[i]-'a'][i]=1;
for(int i=1;i<=ls;i++){
char c=s[i];
for(int j=lx;j>=1;j--){
f[j]=(f[j]<<1)&g[c-'a'];
if(c==x[j])f[j]|=f[j-1];
f[j][0]=f[j][ly];
}
f[0]=f[lx];
}
cout<<(f[0][0]?"Yes":"No")<<"\n";
for(auto &i:f)i.reset();
for(auto &i:g)i.reset();
}
return 0;
}
发现 DP 有很多冗余状态且不好判断,考虑使用记搜。
当一个状态已经合法时直接记录答案并返回,这样可以在合法时剪掉一半的搜索树。
理论复杂度仍然为 \(O(|s||x||y|)\),但实际上在记忆化和剪枝的双重作用下很难卡掉。
(期待各位能造出数据)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int T;
char s[N],x[55],y[55];
int lenx,leny,lens;
map<int,map<int,int> > f[N],vis[N];
bool dfs(int ls,int lx,int ly){
if(ls==lens)return lx==0&&ly==0;
if(vis[ls][lx][ly])return f[ls][lx][ly];
if(s[ls]==x[lx]){
if(dfs(ls+1,(lx+1)%lenx,ly)){
vis[ls][lx][ly]=1;
return f[ls][lx][ly]=1;
}
}
if(s[ls]==y[ly]){
if(dfs(ls+1,lx,(ly+1)%leny)){
vis[ls][lx][ly]=1;
return f[ls][lx][ly]=1;
}
}
vis[ls][lx][ly]=1;
return f[ls][lx][ly]=0;
}
int main(){
freopen("geometry.in","r",stdin);
freopen("geometry.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>s>>x>>y;
lens=strlen(s);lenx=strlen(x);leny=strlen(y);
for(int i=0;i<lens;i++)f[i].clear(),vis[i].clear();
cout<<(dfs(0,0,0)?"Yes":"No")<<"\n";
}
return 0;
}
B 分析
称代价为 A 的操作为 A 操作,另一种为 B 操作。
首先我们假定只有 B 操作。
称度数为奇数的点为奇点,反之为偶点;
显然,奇点的个数一定是一个偶数。
发现每次行走的端点一定均为奇点,因为只有当一个点作为端点的时候它度数的奇偶性才会改变;
所以每进行一次 B 操作,奇点个数都会减少两个;
所以,设奇点个数为 \(cntb\),则总共需要进行 B 操作的次数为 \(\frac{cntb}{2}-1\) 次。
接下来考虑 A 操作。
由于 A 操作的作用是使一条边可以重复经过,所以其效果等价于在两点之间增加一条重边。
所以,我们只需要考虑哪些边需要加重边,统计答案即可;
具体地,假设现在增加了 \(cnta\) 条重边,加边后的奇点数为 \(cntb\),则此时的答案为 \(A \times cnta + B \times (\frac{cntb}{2}-1)\)。
我们设 \(f_{x,0/1,0/1}\) 表示:
对于第 \(x\) 个节点,其 是/不是 奇点,其子树内(不含该点) 有/没有 奇点时代价的最小值;
(实际上,\(f_{x,1,0}\) 这种情况并不存在,因为一条边连接两个点,如果当前节点为奇点其内部一定有一个奇点,但是为了方便之后转移可以直接这么设计状态)
下文的“子树奇偶性”指“子树内是否有奇点”,有奇点即为奇性;“奇偶性为 \(1\)”表示奇性。
那么现在我们要合并 \(u\) 与它的子节点 \(v\),相当于我们要在两点间加一条边:
-
如果我们采用 A 操作,那么相当于我们加了两条边,\(u\) 点与 \(v\) 点的奇偶性均不变;
所以合并后 \(u\) 点子树的奇偶性取决于原来 \(u\) 的子树奇偶性,\(v\) 点奇偶性以及 \(v\) 的子树奇偶性;
显然如果这三者有其中一个为 \(1\),那么合并后 \(u\) 子树的奇偶性就为 \(1\),否则为 \(0\);
于是我们有转移方程 \(f_{u,j1,j2}+f_{v,k1,k2}+A \to f_{u,j1,j2 \operatorname{or} k1 \operatorname{or} k2}\); -
否则,我们只会在 \(u\),\(v\) 之间加一条边,那么此时 \(u\),\(v\) 两点的奇偶性均会改变;
- 如果原先为一奇一偶,改完后仍为一奇一偶,无影响;
- 如果原先均为奇点,更改完后奇点个数减少 \(2\),代价减少 \(B\);
- 如果原先均为偶点,更改完后奇点个数增加 \(2\),代价增加 \(B\);
转移时,由于 \(u\) 点和 \(v\) 点的奇偶性均发生了变化,所以转移时要将其状态取反再进行或运算。
转移方程为 \(f_{u,j1,j2}+f_{v,k1,k2}+A \to f_{u,j1 \otimes 1,j2 \operatorname{or} (k1 \otimes 1) \operatorname{or} k2}\);
最后输出 \(\min(f_{1,0,0},f_{1,0,1}-B,f_{1,1,1}-B)\) 即可。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=5e5+5;
const int inf=1e18;
int n,a,b;
struct edge{
int next,to,w;
}e[N*2];
int h[N],cnt;
void add(int u,int v){
e[++cnt]={h[u],v};
h[u]=cnt;
}
int f[N][2][2],g[2][2];
void dfs(int x,int fa){
f[x][0][0]=0;
f[x][0][1]=f[x][1][0]=f[x][1][1]=1e18;
for(int i=h[x];i;i=e[i].next){
int to=e[i].to;
if(to==fa)continue;
dfs(to,x);
for(int j=0;j<=1;j++)for(int k=0;k<=1;k++)g[j][k]=f[x][j][k],f[x][j][k]=inf;
for(int j1=0;j1<=1;j1++)for(int j2=0;j2<=1;j2++)
for(int k1=0;k1<=1;k1++)for(int k2=0;k2<=1;k2++){
f[x][j1][j2|k1|k2]=min(f[x][j1][j2|k1|k2],g[j1][j2]+f[to][k1][k2]+a);
ll tmp=0;
if(j1&&k1)tmp=-b;
else if((!j1)&&(!k1))tmp=b;
f[x][j1^1][j2|(k1^1)|k2]=min(f[x][j1^1][j2|(k1^1)|k2],g[j1][j2]+f[to][k1][k2]+tmp);
}
}
}
main(){
freopen("analyse.in","r",stdin);
freopen("analyse.out","w",stdout);
cin>>n>>a>>b;
for(int i=1,x,y;i<n;i++){
cin>>x>>y;
add(x,y);add(y,x);
}
dfs(1,0);
cout<<min({f[1][0][0],f[1][1][1]-b,f[1][0][1]-b});
return 0;
}
C 代数
发现 \(k\) 次方这个东西并不好弄,考虑转化为任意选出 \(k\) 个点,都在 \(u\) 子树内的概率乘上 \(n^k\);
我们添加 \(k\) 个点 \(n+1,n+2,...n+k\),让这些点的父亲在 \([1,n]\) 中随机;
将这 \(k\) 个父亲作为选出的 \(k\) 个点,也就是要求 \(k\) 个父亲都在 \(u\) 子树内的概率,也就是这 \(k\) 个点都在 \(u\) 子树内的概率。
设 \(f_{i,j}\),表示已经加入了 \([i,n+k]\) 的所有点,\(n+1 \sim n+k\) 这 \(k\) 个数分到了 \(j\) 棵树里,那么我们需要求出每个点 \(u\) 的 \(f_{u,1}\);
由于 \(f_i\) 只比 \(f_{i-1}\) 多了一个点 \(i\),所以我们直接枚举 \(f_{i-1,j}\) 中连接到 \(i\) 上的子树个数 \(p\);
有转移 \(f_{i,j-p+1} \gets f_{i,j-p+1} + f_{i+1,j} \times {j \choose p} \times \frac{(i-1)^{j-p}}{i^j}\)
答案即为 \(\sum_{i=1}^n \limits f_{i,1}\)
复杂度是 \(O(nk^2)\) 的,所以要提前处理一部分逆元。
点击查看代码
#include<bits/stdc++.h>
#pragma GCC optimize(3)
#define int ll
using namespace std;
typedef long long ll;
const int N=1e5+50;
const int mod=1e9+7;
int n,k,a[N];
inline int qp(int a,int b,int mod=mod){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
int fac[N],inv[N];
int _[N];
int C(int n,int m){
if(m>n)return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void init(int n,int k){
fac[0]=inv[0]=1;
for(int i=1;i<=n+k;i++)fac[i]=fac[i-1]*i%mod;
inv[n+k]=qp(fac[n+k],mod-2);
for(int i=n+k-1;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++)_[i]=qp(i,mod-2);
}
int f[N][25];
int ans;
main(){
freopen("algebra.in","r",stdin);
freopen("algebra.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
init(n,k);
for(int i=1;i<=n;i++)cin>>a[i];
f[n+1][k]=1;
for(int i=n;i>=1;i--){
for(int j=1;j<=k;j++){
for(int p=1;p<=j;p++){
f[i][j-p+1]+=f[i+1][j]*C(j,p)%mod*qp(i-1,j-p)%mod*qp(_[i],j)%mod;
f[i][j-p+1]%=mod;
}
}
ans+=f[i][1]*qp(n,k)%mod*a[i]%mod;
ans%=mod;
for(int j=1;j<=k;j++){
f[i][j]+=f[i+1][j]*qp(i-1,j)%mod*qp(_[i],j)%mod;
f[i][j]%=mod;
}
}
cout<<ans;
return 0;
}
考虑数学做法。
节点 \(x\) 在其子树大小为 \(i\) 的时候的方案数有 \((n-x)!(x-1)!\dbinom{n-i-1}{x-2}\) 种。
由于 \(x\) 的子树大小为 \(i\),所以除 \(x\) 自己外其子树中有 \(i-1\) 个点;
可能成为 \(x\) 的子树中的点的数有 \(n-x\) 个,则选出这 \(i-1\) 个点的方案数有 \(\dbinom{n-x}{i-1}\) 种;
这 \(i-1\) 个数的父亲只能在这 \(i\) 个数中或者是 \(x\),这部分的方案数为 \((i-1)!\);
考虑无关点的贡献。不在 \(x\) 子树内的点有 \(n-i\) 个,这部分点的方案数为 \((n-i-1)!\);
考虑 \(x\) 自己,其有 \((x-1)\) 种方案;
所以总方案数为 \((x-1)(i-1)!(n-i-1)!{n-x \choose i-1} = \frac{(x-1)(n-x)!(n-i-1)!}{(n-x-i+1)!} = (n-x)!(x-1)!{n-i-1 \choose x-2}\)。
该式子中 \((n-x)!(x-1)!\) 与 \(i\) 无关,且由于求的是期望,所以可以直接删去。
问题转化为求
\[\sum_{x=1}^n \ \ a_x \times \frac{\displaystyle\sum_{i=1}^{n-x+1} i^k\dbinom{n-i-1}{x-2}}{\displaystyle\sum_{i=1}^{n-x+1}\dbinom{n-i-1}{x-2}} \]根据范德蒙德卷积,有
\[\displaystyle\sum_{i=0}^{n}\dbinom{i}{a}\dbinom{n-i}{b}=\dbinom{n+1}{a+b+1} \]令 \(i=n-i+1\),那么原式的下半部分可化为
\[\sum_{i=0}^{n-2}\dbinom{i}{x-2}= \sum_{i=0}^{n-2}\dbinom{i}{x-2}\dbinom{n-2-i}{0}=\dbinom{n-1}{x-1} \]上半部分可化为
\[\sum_{i=0}^{n-1}(n-i-1)^k\dbinom{i}{x-2}=\sum_{i=0}^{n-2}(n-i-1)^k\dbinom{i}{x-2} \]继续关注上半部分。
普通幂转下降幂,有公式
(\({n\brace k}\) 为第二类斯特林数)
设 \(c_i={k\brace i} \times i!\),那么
\[x^k=\sum_{i=0}^kc_i\frac{x^\underline i}{i!}=\sum_{i=0}^kc_i\frac{x!}{i!(x-i)!}=\sum_{i=0}^kc_i\dbinom{x}{i} \]带入原式,有
\[\sum_{i=0}^{n-2}(n-i-1)^k\dbinom{i}{x-2}=\sum_{j=0}^kc_j\sum_{i=0}^{n-1}\dbinom{n-i-1}j\dbinom{i}{x-2} \]再次使用范德蒙德卷积,得到
\[\sum_{j=0}^kc_j\sum_{i=0}^{n-1}\dbinom{n-i-1}j\dbinom{i}{x-2}=\sum_{j=0}^kc_j\dbinom{n}{x+j-1} \]这样,我们只需要预处理出每一个 \(c_i\),就可以 \(O(nk)\) 解决该问题。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=1e5+50;
const int mod=1e9+7;
int n,k,a[N];
inline int qp(int a,int b,int mod=mod){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
int s[25][25];
int fac[N],inv[N];
int c[N];
int C(int n,int m){
if(m>n)return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void init(int n,int k){
fac[0]=inv[0]=1;
for(int i=1;i<=n+k;i++)fac[i]=fac[i-1]*i%mod;
inv[n+k]=qp(fac[n+k],mod-2);
for(int i=n+k-1;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
s[0][0]=1;
for(int i=1;i<=k;i++){
for(int j=1;j<=k;j++){
s[i][j]=(s[i-1][j-1]+j*s[i-1][j]%mod)%mod;
}
}
for(int i=1;i<=k;i++)c[i]=s[k][i]*fac[i]%mod;
}
int ans;
main(){
freopen("algebra.in","r",stdin);
freopen("algebra.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
init(n,k);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
int sum=0;
for(int j=0;j<=k;j++){
sum+=C(n,i+j-1)*c[j]%mod;
sum%=mod;
}
ans+=a[i]*sum%mod*qp(C(n-1,i-1),mod-2)%mod;
ans%=mod;
}
cout<<ans;
return 0;
}
(式子由 @oceans_of_stars 和 @jijidawang 两人推出,此处仅为转载)
(参考文章:#C. 代数)
D 组合
神秘题。
如果只有一个数的话,我们可以很简单地二进制拆位做;
现在有两个未知数,所以不能单纯地二进制拆分。
思考一下我们干了点什么;
答案相当于一个只有两位为 \(1\) 的 \(1000\) 位二进制数,而每次询问相当于我们问了一个 \(1000\) 位二进制数,返回的是做按位与的结果;
图中红点表示 \(a\) 和 \(b\),绿点是我们询问的位置;
现在我们把这个图转一下。
我们用过 \(a\),\(b\) 的两条黄线去截这些询问;
这时,我们可以将黄线看做一个 \(26\) 位的二进制数,而这个二进制数中为 \(1\) 就是说询问的位置包含了当前位置。
因为要保证我们的询问能够得到结果,所以我们要做的其实是构造 \(1000\) 个 \(26\) 位二进制数 \(x_1 \sim x_{1000}\),令其对于不同的 \(a\),\(b\) \(x_a \operatorname{or} x_b\) 得到的值不同。
std 给的构造的方式挺神秘的。
std(构造)
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
int N = 20;
int a[1015];
bitset<501500000> bk;
int main() {
int cur = 0;
vector<int> vec;
for (int i = 0; i < (1 << N); i++) {
if (__builtin_popcount(i) == 8)
vec.pb(i);
}
for (int i = 0; i < (1 << N); i++) {
if (__builtin_popcount(i) == 9)
vec.pb(i);
}
for (int i = 1; i <= 1010; i++) {
bool fl2 = 0;
for (auto t : vec) {
a[i] = t;
bool fl = 0;
for (int j = 1; j <= i; j++) {
if (bk[a[i] | a[j]] || (i != j && i <= 996 && __builtin_popcount(a[i] | a[j]) < 11)) {
fl = 1;
for (int k = 1; k < j; k++)
bk[a[i] | a[k]] = 0;
break;
}
bk[a[i] | a[j]] = 1;
}
if (fl)
continue;
fl2 = 1;
break;
}
if (!fl2) {
N++;
vec.clear();
for (int j = 0; j < (1 << N); j++) {
if (__builtin_popcount(j) == 8)
vec.pb(j);
}
if (N >= 24) {
for (int j = 0; j < (1 << N); j++) {
if (__builtin_popcount(j) == 9)
vec.pb(j);
}
}
if (N == 26) {
for (int j = 0; j < (1 << N); j++) {
if (__builtin_popcount(j) != 8 && __builtin_popcount(j) != 9)
vec.pb(j);
}
}
i--;
continue;
}
cout << i << ' ' << N << endl;
if (N > 26) {
cur = i - 1;
break;
}
}
for (int i = 1; i <= 1000; i++)
cout << a[i] << ',';
cout << endl;
return 0;
}
把 std 跑出来的 \(a\) 数组拆位之后就是答案。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int a[]={255,1823,2919,3499,3795,5037,5491,6493,7062,9139,9581,10813,11209,11669,13455,16033,17358,17912,19130,21110,22059,22727,26309,27762,28955,31116,33749,34278,35290,36457,37467,38967,42819,45285,45358,48153,50579,52302,53705,54332,61064,67182,67902,70364,72870,75444,77945,79298,79621,82263,83102,84629,88673,89416,92323,99501,102154,104554,107364,111250,121604,122895,132725,134797,135907,137848,138315,139708,140890,148329,148901,149972,153898,155703,159520,161873,166706,169304,174250,179072,185105,187584,193542,197616,202921,205191,209939,210958,221740,229582,232005,241985,246360,263847,264689,267853,268088,270935,276523,278845,281894,282842,291985,295658,297628,302402,304329,311651,312916,321810,324099,330515,332642,339014,346189,349828,360603,369713,376320,379296,386368,394700,396311,406154,406817,418020,430916,445488,450712,475909,479282,509057,526009,529644,531274,531905,532854,536071,537297,541620,543027,547866,550786,551500,561310,563568,569954,574023,581721,582820,590237,592981,599457,610350,621600,625187,627723,641728,656626,669890,674017,688461,689926,694531,723602,724552,729866,757936,788694,793125,795480,797752,806657,820561,860302,866576,923520,926731,934426,938068,944448,960524,1036800,999880,1017989,1051230,1053877,1054534,1056044,1057642,1061560,1067339,1068593,1070800,1073933,1082780,1084070,1091937,1098922,1115335,1118503,1121928,1124784,1134824,1139786,1148466,1153553,1159252,1163564,1180315,1190662,1200206,1206298,1215570,1218712,1222149,1246028,1247345,1270656,1294355,1311698,1316227,1320588,1321186,1323337,1333385,1340512,1360688,1379369,1403920,1441965,1444641,1475876,1542336,1573857,1583320,1590317,1601826,1614276,1615384,1623362,1640324,1663252,1677318,1716756,1722128,1835434,1869835,1872424,1905232,1974913,2007088,2032664,2100444,2102938,2106595,2107471,2110292,2112290,2115378,2120112,2122194,2130235,2133780,2142761,2146845,2151506,2163433,2166113,2166935,2188049,2192396,2196812,2206018,2226177,2228798,2247299,2249356,2263249,2285642,2295331,2298274,2303252,2315360,2328256,2360172,2362649,2369590,2382408,2394277,2412932,2425286,2474040,2491057,2497696,2502789,2564688,2628870,2638029,2646186,2650629,2655656,2657154,2689648,2697380,2706562,2752867,2755684,2765896,2801812,2887793,2892323,2901344,2950676,2953481,2990720,3016778,3051650,3148906,3165508,3171846,3182607,3183072,3189266,3213701,3252362,3277913,3287077,3330082,3353096,3421952,3441348,3477556,3506249,3556482,3606531,3670840,3679505,3681920,3723393,3805706,3885072,4014113,4197605,4199641,4203244,4203579,4204883,4208952,4214672,4215683,4219505,4227900,4229263,4232994,4238664,4248645,4251798,4260698,4266690,4276454,4282520,4297253,4305312,4310120,4331022,4336212,4336678,4342046,4346544,4383753,4391091,4419652,4426755,4460956,4467586,4475000,4479508,4491013,4503620,4530469,4548098,4563720,4590248,4592065,4605002,4620934,4662312,4720488,4744724,4751475,4755076,4758081,4761761,4794393,4801289,4851089,4853831,4885792,4931724,4998305,5003522,5013801,5051410,5056704,5258245,5288130,5296177,5326210,5374324,5383376,5390915,5441928,5472404,5472777,5521828,5578828,5640290,5768215,5779848,5808676,5898840,5966340,6097184,6301066,6311975,6321728,6330456,6359594,6367809,6375446,6424993,6456370,6460800,6488184,6555234,6558345,6578217,6619537,6725952,6799364,6828384,6837505,6850058,6916108,6931472,7106688,7344788,7357720,7362625,7442450,7602262,7651338,8069121,8130584,8392524,8395417,8395862,8399006,8401384,8406754,8409445,8415000,8423344,8431146,8436296,8440105,8446997,8455481,8462923,8473796,8476208,8480006,8489076,8492550,8497537,8507540,8523823,8544913,8564873,8572418,8581408,8585626,8585893,8596288,8615040,8655270,8660306,8665200,8668268,8671758,8676228,8717249,8717327,8751682,8784322,8804896,8827920,8847668,8917767,8925722,8931467,8947077,8956994,8958225,8982288,9035810,9046105,9077788,9118816,9177357,9183401,9195587,9229344,9338980,9375784,9437591,9474595,9520163,9571601,9576525,9590789,9602209,9699443,9733762,9736220,9756681,9766304,9806096,9847828,9932802,9973860,9978200,9996680,10027714,10207240,10365952,10490697,10494373,10513448,10537296,10563666,10619164,10649987,10689152,10760268,10785801,10863360,10944736,11011155,11015716,11100304,11113024,11150850,11534542,11541633,11576368,11622432,11800784,11846720,12093445,12190468,12321376,12596612,12599375,12620114,12632968,12715331,12722738,12749000,12785924,12846617,12852234,12871840,12916032,13107758,13127984,13177985,13256256,13287441,13371922,13635330,13666864,13676584,13699217,13776384,13779233,14237764,14290962,14418689,14717104,14731777,14745772,14822528,14844200,15468609,15729600,15786240,16130052,16320000,16783304,16789092,16789813,16791642,16791939,16797318,16803153,16813361,16814265,16816653,16821270,16827298,16834920,16843319,16845288,16860697,16876361,16879248,16893989,16896138,16908635,16914068,16917984,16935173,16950028,16978246,16984162,16998674,17011240,17040171,17062224,17072470,17076833,17096837,17106514,17118472,17180848,17188008,17190985,17242129,17302862,17306393,17309895,17324073,17367691,17425416,17438304,17451036,17461384,17487104,17515056,17539089,17569954,17575296,17581330,17629860,17728066,17828689,17839171,17857056,17865800,17875156,17913092,17948832,17965762,17973414,18023569,18058244,18091146,18104853,18123137,18129666,18221156,18367176,18401297,18415674,18487680,18490632,18685957,18885260,18886896,18926176,18941979,18944312,18964547,19039753,19121154,19137621,19192832,19268482,19343364,19401238,19425360,19476994,19480968,19943449,19989092,20022312,20186500,20219910,20328464,20448464,20461569,20465184,20973226,20996280,21016587,21042288,21053793,21121545,21140516,21171968,21235758,21246530,21512532,21528966,21660289,21860416,21901316,22026416,22044686,22086560,22155525,22283352,22446097,22544549,22761472,22815776,23077013,23102785,23205956,23225600,23300224,23335684,23600136,23658570,23889024,24117347,24265220,25034754,25185456,25186362,25192970,25206988,25236037,25330260,25437348,25464906,25496076,25694610,25701152,25762112,25853987,26217824,26222652,26329153,26423297,26494994,26772993,26814480,27001924,27267363,27284548,27330336,27397648,27476001,27525688,27820808,27934726,29360820,29361545,29442256,29634577,29754560,30418947,30447620,31522823,31592584,32514128,33030184,31825952,33561060,33564624,33568022,33571545,33572911,33576716,33583472,33590041,33594501,33596046,33613089,33623280,33624267,33630746,33639970,33677712,33687394,33695891,33709076,33710172,33718505,33728564,33734982,33756040,33757520,33777153,33821723,33822983,33824336,33826216,33861794,33876033,33884809,33899587,33919424,33956456,34082442,34091037,34112566,34120488,34141696,34146633,34161058,34161944,34188416,34212548,34230820,34276644,34343840,34418784,34473009,34496642,34606362,34632321,34640081,34644043,34669116,34673473,34695300,34735288,34739715,34841616,34867604,34876452,34882118,34996568,35046408,35137571,35217473,35280000,35303425,35394566,35422560,35457538,35656805,35666072,35692721,35719948,35735120,35783239,35787034,35864728,35934880,35948872,36061569,36078660,36200518,36214080,36225297,36290596,36464648,36706325,36708745,36767490,36834176,36856352,36966540,37224626,37617729,37749234,37804041,37819468,37849378,37880971,37888900,37925392,38011670,38014273,38043725,38110736,38291602,38306200,38342705,38379526,38818068,38855232,38872328,38879393,39012354,39098624,39129218,39323692,39324752,39856800,40009765,40370821,40517672,40567104,40972800,41176064,41949738,41964930,42033201,42043411,42074901,42087688,42107466,42221874,42238101,42299400,42337924,42471664,42492736,42549765,42762506,42958849,43008396,43061274,43393058,43827712,44048500,44077123,44117008,44353568,44443649,44701698,45090084,45621776,46203942,46252056,46336513,46408384,46441473,46475268,46661925,47192130,47321220,47382568,48251074,49545225,50349258,50373155,50430260,50474024,50486016,50667713,50675846,50728988,50759683,50858098,50868612,50889664,51127810,51139073,51415068,51445976,51490818,51775008,51953924,52445454,52572226,52626592,52709393,52985987,52986904,53813256,54002185,54531218,54535685,54624900,54690128,54812936,55060752,56623920,56692772,56899584,58754272,58756864,58853413,59250700,59310339,59785858,59901008,60097280,60823936,61411344,62935105,63594496,25232514};
int main(){
freopen("combination.in","r",stdin);
freopen("combination.out","w",stdout);
cout<<26<<"\n";
for(int j=0;j<26;j++){
for(auto &i:a){
if(i>>j&1)cout<<1;
else cout<<0;
}
cout<<"\n";
}
return 0;
}
[34] csp-s模拟5
A 光
人类智慧题。
我们发现损耗只有在向下取整的时候才会出现,所以我们考虑不做这个向下取整;
直接枚举是 \(O(n^3)\) 的,所以我们可以贪心地不断将当前最大的数减去 \(4\),直到最小的数小于一个值,然后再去 \(O(n^3)\) 暴力。
复杂度可以算是 \(O(n)\) 的,跑得飞快。
这题还有各种复杂度的做法,但我不会。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a,b,c,d;
int tot;
int main(){
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
cin>>a>>b>>c>>d;
while(1){
int t=max({a,b,c,d});
int tt=min({a,b,c,d});
if(tt<100)break;
if(t==a)a-=4,b-=2,c-=2,d-=1;
else if(t==b)a-=2,b-=4,c-=1,d-=2;
else if(t==c)a-=2,b-=1,c-=4,d-=2;
else a-=1,b-=2,c-=2,d-=4;
tot+=4;
}
int ans=INT_MAX;
for(int i=0;i<=a;i++)for(int j=0;j<=b;j++)for(int k=0;k<=c;k++){
int t1=max(a-i-j/2-k/2,0);
int t2=max(b-i/2-j-k/4,0);
int t3=max(c-i/2-j/4-k,0);
int t4=max(d-i/4-j/2-k/2,0);
int p=max({t1*4,t2*2,t3*2,t4});
ans=min(ans,i+j+k+p);
}
cout<<tot+ans;
return 0;
}
B 爬
对于一个点,它能够产生的贡献只与自己和它的子节点有关。
将这些点拼成一个序列,那么能够凑出的异或和就是这个序列的所有子序列的异或和之和。
我们对这些点的权值拆位,按位考虑。
设现在枚举到第 \(i\) 位,有 \(cnt1\) 个数在这位上为 \(1\),\(cnt0\) 个数在这位为 \(0\);
如果恰好有奇数个数在子序列中,那么它们就可以拼出 \(2^i\) 这个数。
此时的方案数就是 \({cnt1 \choose 1}+{cnt1 \choose 3}+{cnt1 \choose 5}...\),乘上该位权值 \(2^i\),再乘上其它蚂蚁所构成的方案数即可。
注意要特判根节点不能向上跳以及一点只有一只蚂蚁的情况。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int mod=1e9+7;
int n,a[N];
int fa[N];
int _2[N];
vector<int> e[N];
int ans;
inline int qp(int a,int b,int mod=mod){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
int fac[N],inv[N];
void init(int n){
fac[0]=inv[0]=_2[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
inv[n]=qp(fac[n],mod-2);
for(int i=n-1;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++)_2[i]=_2[i-1]*2%mod;
}
int C(int n,int m,int mod=mod){
if(m>n)return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int calc(vector<int> x,bool rt){
int cnt0=0,cnt1=0;
int ans=0;
for(int i=0;i<32;i++){
cnt0=cnt1=0;
for(auto &j:x){
if(a[j]&(1<<i))cnt1++;
else cnt0++;
}
int tmp=0;
for(int j=(!rt||!(a[1]&(1<<i)));j<=cnt1;j+=2){
tmp+=C(cnt1,j);
tmp%=mod;
}
tmp=tmp*_2[cnt0]%mod;
if(!rt||(a[1]&(1<<i)))tmp-=C(cnt1,!rt);
tmp=tmp*_2[i]%mod;
ans+=tmp;
ans%=mod;
}
return ans;
}
main(){
freopen("climb.in","r",stdin);
freopen("climb.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
init(n);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=2;i<=n;i++){
cin>>fa[i];
e[fa[i]].push_back(i);
}
vector<int> tmp;
tmp=e[1];
ans=(ans+calc(tmp,1)*_2[n-tmp.size()-1]%mod)%mod;
for(int i=2;i<=n;i++){
tmp=e[i];
tmp.push_back(i);
ans=(ans+calc(tmp,0)*_2[n-tmp.size()-1]%mod)%mod;
}
cout<<ans;
return 0;
}
C 字符串
首先我们枚举 A
和 B
间切换了多少次。
假设我们的 A
和 B
按照 B...BAB...BA
的方式排列,那么切换 \(i\) 次的权值即为 \(i \times 2\);
然后考虑把剩下的 A 用完。
发现我们可以先把一个 A 放在第一段 B 前面,这样可以直接使权值增加 \(1\);
对于剩下的 A,每有 \(a\) 个 A 都可以使权值加 \(1\)。
然后考虑 B。
首先和 A 一样,我们可以简单地把一个 B 放在最后一个 A 的后面来获取 \(1\) 的贡献;
其次我们来考虑剩下的 B。
发现无论如何,我们先把每一个长度为 \(c\) 的 B 段填充到下一个能使权值加 \(1\) 的做法一定不劣,因为这样做使权值加 \(1\) 的代价最多是 \(b\) 个 B,所以我们可以先把所有的 B 段填满;
对于剩下的 B 同样每有 \(b\) 个 B 可以使权值加 \(1\)。
取每种情况的最大值即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int T;
int n,m,a,b,c;
int ans;
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n>>m>>a>>b>>c;
ans=0;
for(int i=0;i<=min(n,m/c);i++){
int now=i*2;
if(n-i>=1){
now++;
now+=(n-i-1)/a;
}
if(c>b)now+=(c-1)/b*i;
if(m-c*i>=1){
now++;
int res=m-c*i-1;
int t=0;
if(c>b)t=b-(c-1)%b;
else t=b+1-c;
if(t){
int d=min(res/t,i);
now+=d;
res-=d*t;
}
now+=res/b;
}
ans=max(ans,now);
}
cout<<ans<<"\n";
}
return 0;
}
D 奇怪的函数
首先转化一下题意。
将 \(\min\) 操作看成是对上界的限制,\(\max\) 操作看成是对下界的限制,那么我们可以将每一个操作都看成一个分段函数;
那么,所有区间都是一个分 \(3\) 段的分段函数,形如
简单的证明
第一个函数 \(f(x)\) 相当于给 \(x\) 做了一个限制;
对于一个 \(x\),\(f(x)\) 只会对应 \(l_f+val_f\),\(r_f+val_f\) 或 \(x+val_f\);
对于前两种,\(g(f(x))\) 的值是固定的;
对于第三种,由于 \(g(x)\) 分三段,且两函数上升部分斜率一样,所以 \(g(f(x))\) 随 \(f(x)\) 的增长而增长,即随 \(x\) 增长而增长;
而这三段是首尾相接的,所以 \(g(f(x))\) 也为一个分 \(3\) 段的函数。
详细的证明
首先,当区间长度为 \(1\) 时,其为一个分段函数,这是显然的。
考虑合并两个分 \(3\) 段的函数。设两函数分别为 \(f(x),g(x)\),则合并后的结果为 \(g(f(x))\)。
现在我们画出 \(f(x)\)。
看着不太好弄。
我们把它翻转一下再旋转一下,然后把 \(g(x)\) 也塞进来;
(蓝色的 \(x,y\) 是针对 \(y=f(x)\) 而言的,橙色的 \(x',y'\) 是针对 \(y'=g(x')\) 而言的)
这样,对于一点 \(p\),\(g(f(p))\) 如下;
所以我们可以简单地寻找出两个端点,这两个端点就是新分段函数的 \(l\) 与 \(r\)。
所以,将两个分 \(3\) 段函数合并起来,得到的函数也分 \(3\) 段。
所以我们只需要用一棵线段树来维护 \([l,r]\) 内的分段函数,\(O(\log n)\) 修改,\(O(1)\) 查询即可。
注意要额外维护 \(l>r\) 的情况,此时为常数函数。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int inf=1e9;
int n,m;
struct tree{
int l,r,val;
int ans;
/*
l if x < l
f(x)={ x if l <= x <= r } + val
r if x > r
*/
}t[N<<2];
void pushup(int k){
t[k].val=t[k<<1].val+t[k<<1|1].val;
t[k].l=max(t[k<<1].l,t[k<<1|1].l-t[k<<1].val);
t[k].r=min(t[k<<1].r,t[k<<1|1].r-t[k<<1].val);
if(t[k<<1|1].r<=t[k<<1|1].l)t[k].ans=t[k<<1|1].ans;
else t[k].ans=min(t[k<<1|1].r,max(t[k<<1|1].l,t[k<<1].ans))+t[k<<1|1].val;
}
void build(int k,int l,int r){
t[k]={-inf,inf,0,0};
if(l==r)return;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(k);
}
void update(int k,int l,int r,int pos,int op,int val){
if(l==r){
if(op==1)t[k]={-inf,inf,val,0};
else if(op==2)t[k]={-inf,val,0,0};
else t[k]={val,inf,0,0};
return;
}
int mid=(l+r)>>1;
if(pos<=mid)update(k<<1,l,mid,pos,op,val);
else update(k<<1|1,mid+1,r,pos,op,val);
pushup(k);
}
int query(int x){
if(t[1].r<=t[1].l)return t[1].ans;
else return min(t[1].r,max(t[1].l,x))+t[1].val;
}
int main(){
freopen("function.in","r",stdin);
freopen("function.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
build(1,1,n);
for(int i=1,op,val;i<=n;i++){
cin>>op>>val;
update(1,1,n,i,op,val);
}
cin>>m;
for(int i=1,op,x,y;i<=m;i++){
cin>>op;
if(op<4){
cin>>x>>y;
update(1,1,n,x,op,y);
}
else{
cin>>x;
cout<<query(x)<<"\n";
}
}
return 0;
}
[35] csp-s模拟6
A 一般图最小匹配
首先我们有一个结论:将 \(a\) 数组排序后,一个数只会选择它左右的数。
所以我们将 \(a\) 数组排序后差分成 \(b\) 数组,那么问题就转化为:在 \(b\) 数组中选出 \(m\) 个数,选出的数不能相邻,求最小权值。
然后这题其实就变成了 P1484 种树,可反悔贪心板子。
设 \(l_u\) 为 \(u\) 的前驱,\(r_u\) 为 \(u\) 的后继,\(val_u\) 为 \(u\) 的权值;
我们先将所有数插入堆中,每次取出堆顶,设该点为 \(u\);
此时 \(u\) 的权值是最小的,用其更新答案,并将 \(l_u\) 和 \(r_u\) 删除;
接下来我们将 \(val_{l_u}+val_{r_u}-val_u\) 插入堆中,将其看做一个元素,也可以被选择;
当这个元素被弹出时,说明我们进行了一次反悔,放弃选择 \(u\),改为选择 \(l_u\) 和 \(r_u\);
复杂度 \(O(m \log n)\)。
为了保证复杂度,删除要用懒惰删除。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=5e3+5;
int n,m,a[N];
int b[N];
int l[N],r[N];
int vis[N];
struct node{
int id,val;
bool operator < (const node &a)const{
return val>a.val;
}
};
priority_queue<node> q;
ll ans;
main(){
freopen("match.in","r",stdin);
freopen("match.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
for(int i=1;i<n;i++){
b[i]=a[i+1]-a[i];
l[i]=i-1;
r[i]=i+1;
q.push({i,b[i]});
}
b[0]=b[n]=1e17;
for(int i=1;i<=m;i++){
while(!q.empty()&&vis[q.top().id])q.pop();
if(q.empty())continue;
node tmp=q.top();
q.pop();
ans+=tmp.val;
int id=tmp.id;
vis[l[id]]=vis[r[id]]=1;
b[id]=b[l[id]]+b[r[id]]-b[id];
q.push({id,b[id]});
l[id]=l[l[id]];
r[id]=r[r[id]];
r[l[id]]=l[r[id]]=id;
}
cout<<ans;
return 0;
}
观察到此题 \(n,m \le 5000\),由于可反悔贪心跑得太快,考虑 DP。
设 \(f_{i,j}\) 表示考虑到第 \(i\) 个数,选了 \(j\) 对的最小值,有转移 \(f_{i,j} \gets \min(f_{i-1,j} \ , \ f_{i-2,j-1}+a_i-a_{i-1})\)。
复杂度 \(O(nm)\)。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=5e3+5;
int n,m,a[N];
int f[N][N];
main(){
freopen("match.in","r",stdin);
freopen("match.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
memset(f,0x3f,sizeof(f));
for(int i=0;i<=n;i++)f[i][0]=0;
for(int i=2;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=min(f[i-1][j],f[i-2][j-1]+a[i]-a[i-1]);
}
}
cout<<f[n][m];
return 0;
}
B 重定向
分讨。
从前往后扫,扫到每一位时进行如下操作:
- 如果 \(a_i>a_{i+1}\),直接删除该位。
- 如果 \(a_{i+1}=0\),那么假设删去第 \(i\) 位后第 \(i+1\) 位应该填的是 \(x\),若 \(x<a_i\) 则删除该位;
- 如果 \(a_i=0\),设第 \(i\) 位原本要填的数是 \(x\),如果存在 \(j\) 使 \(a_j<x\) 且 \(j>i\),那么可以将 \(j\) 删除后将 \(a_j\) 放在 \(i\) 处,所以选择删。
定好删的点后就直接扫一遍,从小到大填就好了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
const int inf=1e9;
int T,n;
int a[N];
int pos[N],vis[N],minn[N];
int main(){
freopen("repeat.in","r",stdin);
freopen("repeat.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n;
memset(vis,0,sizeof(vis));
memset(pos,0,sizeof(pos));
memset(minn,0,sizeof(minn));
priority_queue<int,vector<int>,greater<int> > q;
for(int i=1;i<=n;i++){
cin>>a[i];
pos[a[i]]=i;
vis[a[i]]=1;
}
minn[n+1]=inf;
for(int i=n;i>=1;i--)minn[i]=min(minn[i+1],a[i]?a[i]:inf);
for(int i=1;i<=n;i++)if(!vis[i])q.push(i);
int del=n;
for(int i=1;i<n;i++){
if(!a[i]){
if(minn[i]>q.top()){
a[i]=q.top();
q.pop();
}
else{
a[i]=minn[i];
del=pos[a[i]];
break;
}
}
else if(a[i+1]){
if(a[i]>a[i+1]){
del=i;
q.push(a[i]);
break;
}
}
else{
if(a[i]>q.top()){
del=i;
q.push(a[i]);
break;
}
}
}
for(int i=1;i<=n;i++){
if(i==del)continue;
if(!a[i])a[i]=q.top(),q.pop();
cout<<a[i]<<" ";
}
cout<<"\n";
}
return 0;
}
C 斯坦纳树
答案正确,仅当由所有询问点构成的虚树中的点全部在询问序列中。
所以其实可以直接用虚树做了。
但是伟大的 @int_R 为我们提供了不需要虚树知识的做法:
维护一个集合;
每加入一个点,暴力向上跳,标记所有经过的点;
跳到一个已经被标记的点就结束,并将这个点加入该集合。
比较这个集合是否包含于当前的询问序列即可。
本质上还是虚树解法,但是好打。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
int n;
struct edge{
int next,to,w;
}e[N*2],ee[N];
int h[N],cnt;
void add(int u,int v,int w){
e[++cnt]={h[u],v,w};
h[u]=cnt;
}
int fa[N];
int find(int x){
if(x!=fa[x])fa[x]=find(fa[x]);
return fa[x];
}
int a[N];
int ffa[N];
int vis[N];
void dfs(int x,int f){
ffa[x]=f;
for(int i=h[x];i;i=e[i].next){
int to=e[i].to;
if(to==f)continue;
dfs(to,x);
}
}
bool p[N],pp[N];
set<int> s,in;
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1,u,v,w;i<n;i++){
cin>>u>>v>>w;
ee[i]={u,v,w};
if(!w)fa[find(u)]=find(v);
}
for(int i=1;i<n;i++){
int u=find(ee[i].next),v=find(ee[i].to);
if(u==v)continue;
add(u,v,ee[i].w);add(v,u,ee[i].w);
}
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]=find(a[i]);
}
dfs(a[1],0);
vis[a[1]]=1;
s.insert(a[1]);
cout<<1;
int tt=0;
for(int i=2;i<=n;i++){
int now=a[i];
if(in.count(now))in.erase(now);
s.insert(now);
while(!vis[now]){
vis[now]=1;
now=ffa[now];
}
if(!s.count(now))in.insert(now);
cout<<in.empty();
}
return 0;
}
D 直径
神秘 DP。不会。挂一下官方题解。
int_R
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>
#define int long long
#define ll long long
using namespace std;
const int N=45,mod=998244353;
ll n,K,p,F[N][N][N],G[N][N][N][N],S[N],I[N];
inline void A(ll &x,ll y)
{x+=y;if(x>=mod) x-=mod;}
inline ll ksm(ll a,int b=mod-2)
{
ll ans=1;for(;b;b>>=1,a=a*a%mod)
if(b&1) ans=ans*a%mod;
return ans;
}
signed main()
{
freopen("dia.in","r",stdin);
freopen("dia.out","w",stdout);
cin>>n>>K>>p,F[1][1][1]=1;
for(int i=1;i<=n;++i) I[i]=ksm(i);
for(int d=2;d<=K/2+1;++d)//枚举深度
{
ll f[N][N][N];memset(f,0,sizeof(f));f[0][1][0]=1;
for(int k=1;k<n;++k)//枚举转移大小 (限制最大深度 d-2)
{
for(int c=1;c<=min(n/k,S[k]);++c)//枚举同一大小选几种
{
for(int x=1;x<=n/k;++x)//枚举这一种选几个
{
for(int s=n;s>=x*k;--s)//枚举现在大小
{
A(f[c][s][0],f[c-1][s-x*k][0]*(S[k]-c+1)%mod*I[c]%mod);
}
}
}
for(int c=1;c<=min(n/k,S[k]);++c) for(int s=1;s<=n;++s)
A(f[0][s][0],f[c][s][0]),f[c][s][0]=0;
}
if(d==K/2+1&&(K&1^1)){for(int s=0;s<=n;++s) G[0][s][0][0]=f[0][s][0];break;}
for(int k=d-1;k<n;++k)//枚举转移大小(限制深度为 d-1)
{
for(int y=1;y<=k;++y)//枚举关键叶子个数
{
for(int c=1;c<=min(n/k,F[d-1][k][y]);++c)//枚举同一大小同一关键叶子个数选几种
{
for(int x=1;x<=n/k;++x)//枚举这一种选几个
{
for(int s=n;s>=x*k;--s)//枚举现在大小
{
for(int z=s;z>=x*y;--z)//枚举现在关键叶子个数
{
A(f[c][s][z],f[c-1][s-x*k][z-x*y]*(F[d-1][k][y]-c+1)%mod*I[c]%mod);
}
}
}
}
for(int c=1;c<=min(n/k,F[d-1][k][y]);++c) for(int s=1;s<=n;++s) for(int z=1;z<=s;++z)
A(f[0][s][z],f[c][s][z]),f[c][s][z]=0;
}
}
for(int s=1;s<=n;++s) for(int z=1;z<=s;++z) F[d][s][z]=f[0][s][z];
for(int s=1;s<=n;++s) for(int z=1;z<=s;++z) A(S[s],F[d-1][s][z]);
}
int d=K/2+1;
if(K&1)
{
ll ans=0;
for(int k1=1;k1<=n;++k1)
{
for(int y1=1;y1<=k1;++y1)
{
if(p%y1) continue;
int k2=n-k1,y2=p/y1;
if(k2<k1||(k2==k1&&y2<y1)) continue;
ans+=F[d][k1][y1]*F[d][k2][y2]%mod;
}
}
cout<<ans%mod<<'\n';return 0;
}
for(int k=d-1;k<n;++k)//枚举转移大小(限制深度为 d-1)
{
for(int y=1;y<=k;++y)//枚举关键叶子个数
{
for(int c=1;c<=min(n/k,F[d-1][k][y]);++c)//枚举同一大小同一关键叶子个数选几种
{
for(int x=1;x<=n/k;++x)//枚举这一种选几个
{
for(int s=n;s>=x*k;--s)//枚举现在大小
{
for(int z=s-x*y;z>=0;--z)//枚举转移前关键叶子个数
{
for(int a=p-(z*x*y+(y*y*x*(x-1)/2));a>=0;--a)//枚举转移前直径个数
{
A(G[c][s][z+x*y][a+z*x*y+(y*y*x*(x-1)/2)],G[c-1][s-x*k][z][a]*(F[d-1][k][y]-c+1)%mod*I[c]%mod);
}
}
}
}
}
for(int c=1;c<=min(n/k,F[d-1][k][y]);++c) for(int s=1;s<=n;++s) for(int z=1;z<=s;++z) for(int a=0;a<=p;++a)
A(G[0][s][z][a],G[c][s][z][a]),G[c][s][z][a]=0;
}
}
ll ans=0;for(int i=1;i<=n;++i) ans+=G[0][n][i][p];
cout<<ans%mod<<'\n';return 0;
}
5k
#include <cstdio>
#include <cstring>
#define M 998244353
#define int long long
bool F;
int n, k, p, o, v[50], g[50][50][50], G[50][50];
int C(int n, int k)
{
int q = 1;
for (int i = n + k - 1; i >= n; --i)
q = q * i % M;
for (int i = 1; i <= k; ++i)
q = q * v[i] % M;
return q;
}
signed main()
{
freopen("dia.in", "r", stdin);
freopen("dia.out", "w", stdout);
v[1] = 1;
for (int i = 2; i <= 40; ++i)
v[i] = (M - M / i) * v[M % i] % M;
scanf("%lld%lld%lld", &n, &k, &p);
if (k & 1)
F = 1;
k = k + 1 >> 1;
g[1][1][1] = 1;
for (int vn = 1; vn <= n; ++vn)
for (int vl = 1; vl <= k; ++vl)
for (int vp = 1; vp <= vn; ++vp)
for (int un = n - vn; un >= 1; --un)
for (int ul = 1; ul <= k; ++ul)
for (int up = 1; up <= un; ++up)
for (int t = 1; un + vn * t <= n; ++t)
{
int _ = C(g[vn][vl][vp], t);
if (vl + 1 > ul)
g[un + vn * t][vl + 1][vp * t] = (g[un + vn * t][vl + 1][vp * t] + g[un][ul][up] * _) % M;
else if (vl + 1 == ul)
g[un + vn * t][ul][up + vp * t] = (g[un + vn * t][ul][up + vp * t] + g[un][ul][up] * _) % M;
else
g[un + vn * t][ul][up] = (g[un + vn * t][ul][up] + g[un][ul][up] * _) % M;
}
for (int vn = 1; vn <= n; ++vn)
for (int vp = 1; vp <= vn; ++vp)
{
G[vn][vp] = (G[vn][vp] + g[vn][k][vp]) % M;
for (int vl = 1; vl < k; ++vl)
G[vn][0] = (G[vn][0] + g[vn][vl][vp]) % M;
}
if (!F)
{
int f[50][50][50];
memset(f, 0, sizeof f);
f[1][0][0] = 1;
for (int vn = 1; vn <= n; ++vn)
for (int vp = 0; vp <= vn; ++vp)
for (int un = n - vn; un >= 1; --un)
for (int ud = 0; ud <= p; ++ud)
for (int up = 0; up <= un && ud + up * vp <= p; ++up)
{
int _d = ud + vp * up, _p = up + vp;
for (int t = 1; un + vn * t <= n && _p <= n && _d <= p; ++t, _d += vp * _p, _p += vp)
{
int _ = C(G[vn][vp], t);
f[un + vn * t][_p][_d] = (f[un + vn * t][_p][_d] + f[un][up][ud] * _) % M;
}
}
int z = 0;
for (int i = 0; i <= n; ++i)
z = (z + f[n][i][p]) % M;
printf("%lld", z);
}
else
{
int z = 0;
for (int vn = 1; vn <= n; ++vn)
for (int vp = 0; vp <= vn; ++vp)
for (int un = 1; un <= n; ++un)
for (int up = 0; up <= un; ++up)
if (un + vn == n && up * vp == p)
{
int _z = z;
if (un == vn && up == vp)
z = (z + C(G[un][up], 2) * 2) % M;
else
z = (z + G[un][up] * G[vn][vp]) % M;
}
printf("%lld", z * (M + 1 >> 1) % M);
}
return 0;
}
[36] csp-s模拟7
A median
分讨。
枚举中位数,然后看看有多少种情况符合要求。
情况只有(对比现在)
- 2 大 2 小
- 1 大 2 小 1 相等
- 2 大 1 小 1 相等
- 1 大 1 小 2 相等
- 2 大 2 相等
- 2 小 2 相等
- 1 大 3 相等
- 1 小 3 相等
- 4 相等
九种。
大力枚举即可。
打的常数巨大,感觉能卡掉。
点击查看代码
#include<bits/stdc++.h>
#define int ll
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int mod=998244353;
int n,a[6][N];
int cnt[4];
int mid(int a,int b,int c,int d,int e){
vector<int> tmp={a,b,c,d,e};
sort(tmp.begin(),tmp.end());
return tmp[2];
}
int ans;
void s1(){
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=n;k++)
for(int l=1;l<=n;l++)for(int m=1;m<=n;m++){
ans=(ans+mid(a[1][i],a[2][j],a[3][k],a[4][l],a[5][m]))%mod;
}
cout<<ans;
}
int l[6],r[6],s[6];
int calc(int now,int x){
int ans=0;
for(int i=1;i<=5;i++){
if(i==now)continue;
int pos=lower_bound(a[i]+1,a[i]+1+n,x)-a[i];
l[i]=pos-1;
pos=upper_bound(a[i]+1,a[i]+1+n,x)-a[i];
r[i]=n-pos+1;
s[i]=n-l[i]-r[i];
}
for(int i=1;i<=5;i++){// 2 big 2 small
if(i==now)continue;
for(int j=i+1;j<=5;j++){
if(j==now)continue;
for(int p=1;p<=5;p++){
if(p==i||p==j||p==now)continue;
for(int q=p+1;q<=5;q++){
if(q==i||q==j||q==now)continue;
ans+=l[i]*l[j]%mod*r[p]%mod*r[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 1 big 2 small 1 equal
if(i==now)continue;
for(int j=1;j<=5;j++){
if(j==now||i==j)continue;
for(int p=1;p<=5;p++){
if(p==i||p==j||p==now)continue;
for(int q=p+1;q<=5;q++){
if(q==i||q==j||q==now)continue;
ans+=s[i]*r[j]%mod*l[p]%mod*l[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 1 small 2 big 1 equal
if(i==now)continue;
for(int j=1;j<=5;j++){
if(j==now||i==j)continue;
for(int p=1;p<=5;p++){
if(p==i||p==j||p==now)continue;
for(int q=p+1;q<=5;q++){
if(q==i||q==j||q==now)continue;
ans+=s[i]*l[j]%mod*r[p]%mod*r[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 1 big 1 small 2 equal
if(i==now)continue;
for(int j=i+1;j<=5;j++){
if(j==now)continue;
for(int p=1;p<=5;p++){
if(p==i||p==j||p==now)continue;
for(int q=1;q<=5;q++){
if(q==i||q==j||p==q||q==now)continue;
ans+=s[i]*s[j]%mod*l[p]%mod*r[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 2 big 2 equal
if(i==now)continue;
for(int j=i+1;j<=5;j++){
if(j==now)continue;
for(int p=1;p<=5;p++){
if(p==now||p==i||p==j)continue;
for(int q=p+1;q<=5;q++){
if(q==now||q==i||q==j)continue;
ans+=s[i]*s[j]%mod*r[p]%mod*r[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 2 small 2 equal
if(i==now)continue;
for(int j=i+1;j<=5;j++){
if(j==now)continue;
for(int p=1;p<=5;p++){
if(p==now||p==i||p==j)continue;
for(int q=p+1;q<=5;q++){
if(q==now||q==i||q==j)continue;
ans+=s[i]*s[j]%mod*l[p]%mod*l[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 1 big 3 equal
if(i==now)continue;
for(int j=i+1;j<=5;j++){
if(j==now)continue;
for(int p=j+1;p<=5;p++){
if(p==now)continue;
for(int q=1;q<=5;q++){
if(q==i||q==j||p==q||q==now)continue;
ans+=s[i]*s[j]%mod*s[p]%mod*r[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 1 small 3 equal
if(i==now)continue;
for(int j=i+1;j<=5;j++){
if(j==now)continue;
for(int p=j+1;p<=5;p++){
if(p==now)continue;
for(int q=1;q<=5;q++){
if(q==i||q==j||p==q||q==now)continue;
ans+=s[i]*s[j]%mod*s[p]%mod*l[q]%mod;
ans%=mod;
}
}
}
}
for(int i=now;i<=5;i++){// 4 equal
if(i==now)continue;
for(int j=i+1;j<=5;j++){
if(j==now)continue;
for(int p=j+1;p<=5;p++){
if(p==now)continue;
for(int q=p+1;q<=5;q++){
if(q==now)continue;
ans+=s[i]*s[j]%mod*s[p]%mod*s[q]%mod;
ans%=mod;
}
}
}
}
return ans;
}
main(){
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=5;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
sort(a[i]+1,a[i]+1+n);
}
if(n<=20)return s1(),0;
for(int i=1;i<=5;i++){
for(int j=1;j<=n;j++){
ans+=a[i][j]*calc(i,a[i][j])%mod;
ans%=mod;
}
}
cout<<ans;
return 0;
}
B travel
思维题……吧。
首先我们发现只要图中有一个长度 \(\ge 2\) 的环就是合法的,因为假设不绕环从 \(x\) 走到 \(y\) 需要 \(k\) 步,如果环长为 \(l\),那么当步数限制为 \(k+l,k+2l…\) 时方案数均为 \(1\),序列永远不会收敛;
其次,如果有两个自环相连,那么序列也不会收敛。因为每一步我们都可以在第一个自环绕环一周或者在第二个自环绕环一周,所以方案数会越来越多,当 \(x \to \infty\) 时 \(f(x)\) 同样 \(\to \infty\)。
简单判断即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int n,m;
int tot;
struct edge{
int next,to;
}e[N*2];
int h[N],cnt;
void add(int u,int v){
e[++cnt]={h[u],v};
h[u]=cnt;
}
int vis[N];
void dfs(int x,int st){
vis[x]=st;
for(int i=h[x];i;i=e[i].next){
int to=e[i].to;
if(to==x)continue;
if(vis[to])continue;
dfs(to,st);
}
}
int d[N];
bool topsort(){
int tot=0;
queue<int> q;
for(int i=1;i<=n;i++)if(!d[i])q.push(i);
while(!q.empty()){
int u=q.front();
q.pop();
tot++;
for(int i=h[u];i;i=e[i].next){
int to=e[i].to;
d[to]--;
if(!d[to]){
q.push(to);
}
}
}
return tot==n;
}
vector<int> s;
int tt[N];
int main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
if(u==v){
s.push_back(v);
continue;
}
add(u,v);
d[v]++;
}
if(!topsort())return cout<<"Yes",0;
for(int i=1;i<=n;i++){
if(!vis[i])dfs(i,i);
}
for(auto &i:s){
if(tt[vis[i]])return cout<<"Yes",0;
tt[vis[i]]++;
}
cout<<"No";
return 0;
}
C game
博弈论。
首先,当序列中每一个数都出现了偶数次,先手必败。
这是显然的。
考虑将每个数和另一个和它相同的数配对,那么先手无论如何进行操作,后手都能对在同一对内的另一个数进行相同的操作,从而使序列中的数仍能两两配对;这样只要先手能进行操作,后手就能进行操作。
举个例子,现在有 \(x_1,x_2,y_1,y_2,z_1,z_2,...\) 一些能够两两配对的数;
假设先手选择了 \(x_1\),将它减小了 \(\delta\),同时将 \(y_1,z_1\) 分别增加了 \(\Delta y,\Delta z\);
那么后手可以对称且同程度地减小 \(x_2\),增加 \(y_2,z_2\),保证所有数仍能两两配对;
由于局面总是对称的,这样下去,先手一定是最先无法操作的一方。
接下来我们考虑为什么不满足上述条件时先手必胜。
先手必胜,只需要将局面转化为先手必败,也就是“序列中每一个数都出现了偶数次”的局面;
分奇偶考虑。
-
如果序列的长度是奇数
设序列中的出现了奇数次的数分别为 \(a<b<c<...<g\),考虑将所有数的出现次数改成偶数,也就是移除 \(g\),将 \(a\) 补至 \(b\),将 \(c\) 补至 \(d\),以此类推;
那么要满足的条件为 \(g>(b-a)+(d-c)+...\)
移项可得 \(a+c+...+g>b+d+...+f\),由上述 \(a<b<c<...<g\) 可知这是正确的。 -
如果序列的长度是偶数
设序列中的出现了奇数次的数分别为 \(a<b<c<...<h\),考虑将所有数的出现次数改成偶数,也就是将 \(h\) 下降至 \(a\),将 \(b\) 补至 \(c\),将 \(d\) 补至 \(e\),以此类推;
那么要满足的条件为 \((h-a)>(c-b)+(e-d)+...\)
移项可得 \(b+d+...+h>a+c+...+g\),由上述 \(a<b<c<...<h\) 可知这是正确的。
得证。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int T;
int n,a[N];
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
vector<int> res;
for(int i=1;i<=n;i++){
if(a[i]==a[i+1]){
i++;
continue;
}
res.push_back(a[i]);
}
cout<<(res.empty()?"No":"Yes")<<"\n";
}
return 0;
}