test20231102
T1
这是一道简单题,考虑从后往前 dp。复杂度是 \(n^2\) 的。但是可以考虑前缀和优化,因为前缀和有单调性,所以直接上二分。
int n,L;
int a[N];
int f[N],sum[N];
signed main(){
n=read();L=read();
up(i,1,n){
a[i]=read();
sum[i]=sum[i-1]+a[i];
}
f[n]=1;
dn(i,n-1,1){
int l=i,r=n,ans;
while(l<=r){
int mid=(l+r)>>1;
if(sum[mid]-sum[i-1]<=L){
ans=mid;
l=mid+1;
}
else r=mid-1;
}
f[i]=f[ans+1]+1;
}
up(i,1,n){
cout<<f[i]<<" ";
}
return 0;
}
T2
同样是简单题,看起来就很 dp 的样子。
令 \(dp_{i,j,op}\) 为当前节点为 \(i\),里面有 \(j\) 个属于 \(1\) 县城,\(i\) 号节点属于 \(op\) 县城。
那么答案就是 \(\max(dp_{1,n/2,op})\)。
转移方程还是比较简单的,可以看我代码:
inline void dfs(int u,int from){
sz[u]=1;
for(auto i:g[u]){
int v=i.fi,w=i.se;
if(v==u)continue;
dfs(v,u);
sz[u]+=sz[v];
}
if(sz[u]==1){
dp[u][1][1]=0;
dp[u][0][0]=0;
}
else{
up(i,0,min(sz[u],n/2)){
if(sz[u]==2){
dp[u][0][0]=max(dp[u][0][0],dp[u<<1][0][0]+d[u<<1]);
dp[u][1][0]=max(dp[u][1][0],dp[u<<1][1][1]);
dp[u][1][1]=max(dp[u][1][1],dp[u<<1][0][0]);
dp[u][2][1]=max(dp[u][2][1],dp[u<<1][1][1]+d[u<<1]);
continue;
}
up(j,0,i){
int t=i-1;
if(j<=t&&i>=1){
dp[u][i][1]=max(dp[u<<1][j][0]+dp[u<<1|1][t-j][0],dp[u][i][1]);
dp[u][i][1]=max(dp[u<<1][j][1]+dp[u<<1|1][t-j][0]+d[u<<1],dp[u][i][1]);
dp[u][i][1]=max(dp[u<<1][j][0]+dp[u<<1|1][t-j][1]+d[u<<1|1],dp[u][i][1]);
dp[u][i][1]=max(dp[u<<1][j][1]+dp[u<<1|1][t-j][1]+d[u<<1]+d[u<<1|1],dp[u][i][1]);
}
dp[u][i][0]=max(dp[u<<1][j][1]+dp[u<<1|1][i-j][1],dp[u][i][0]);
dp[u][i][0]=max(dp[u<<1][j][0]+dp[u<<1|1][i-j][1]+d[u<<1],dp[u][i][0]);
dp[u][i][0]=max(dp[u<<1][j][1]+dp[u<<1|1][i-j][0]+d[u<<1|1],dp[u][i][0]);
dp[u][i][0]=max(dp[u<<1][j][0]+dp[u<<1|1][i-j][0]+d[u<<1]+d[u<<1|1],dp[u][i][0]);
}
}
}
}
signed main(){
n=read();
up(i,1,n){
d[i]=read();
g[i/2].push_back({i,d[i]});
}
memset(dp,-0x3f,sizeof dp);
dfs(1,0);
cout<<max(dp[1][n/2][1],dp[1][n/2][0]);
return 0;
}
T3
有点意思的一道题,当时考场上没有想出来,打了个搜索就滚了,现在想一想,好像还挺简单的。
因为已经知道叶子的情况,有一个比较显然的东西,就是说每一个子树,如果红色多于蓝色,那么其一定属于红色,如果蓝色多于红色,那么一定属于蓝色。
所以我们可以从叶子开始向上递归,最后判断根节点的颜色。
这样 \(55\) 分就到手了。
如果根节点是红色,那么所有的点都可以选,输出情况也好考虑。
如果根节点是蓝色,那么输出 \(-1\)。
那么如果根节点无法确定怎么办呢?
此时,红子就需要一步把这个节点边红,那么有两种可能,一种是把一个不确定变为红色,一种是把蓝色变为不确定,向下递归就可以了。
int n,f[N];
vector<int>g[N];
inline void dfs1(int u){
if(!g[u].size())return;
int cnt[2]={0,0};
for(auto v:g[u]){
dfs1(v);
if(f[v]!=-1)cnt[f[v]]++;
}
if(cnt[0]>cnt[1])f[u]=0;
else if(cnt[0]==cnt[1])f[u]=-1;
else f[u]=1;
}
vector<int>ans;
inline void dfs2(int u){
if(!g[u].size()){
if(f[u]==-1)ans.push_back(u);
return;
}
if(f[u]==-1){
for(auto v:g[u]){
if(f[v]==0)continue;
dfs2(v);
}
}
else if(f[u]==1){
int cnt[2]={0,0};
for(auto v:g[u]){
if(f[v]!=-1)cnt[f[v]]++;
}
if(cnt[1]-cnt[0]==1){
for(auto v:g[u]){
if(f[v]==0)continue;
dfs2(v);
}
}
}
}
inline void solve(){
n=read();
int x;
up(i,1,n){
x=read();
g[x].push_back(i);
}
up(i,1,n)f[i]=read();
dfs1(1);
if(f[1]==1){
puts("-1");
return;
}
else if(f[1]==0){
up(i,1,n)if(g[i].size()==0&&f[i]==-1)ans.push_back(i);
}
else dfs2(1);
sort(ans.begin(),ans.end());
write(ans.size(),0);
for(auto v:ans)write(v,0);
printf("\n");
}
inline void clear(){
ans.clear();
memset(f,0,sizeof f);
up(i,1,n)g[i].clear();
}
signed main(){
freopen("rab.in","r",stdin);
freopen("rab.out","w",stdout);
int T=read();
while(T--){
clear();
solve();
}
return 0;
}
T4
也是有意思的一道博弈论。
看着似乎完全无法下手的样子,因为它要求所有的的子集的方案数。
一般遇到这种情况,就要考虑一下,是不是有什么特殊的性质。
由于后一个人可以重复上一个人选择的堆,每堆苹果的数量对 \(a+b\) 取模不改变胜负状态。
这种判断可以运用在许多博弈论里面。
令 \(a<b\):
- \(x_i < a\),这一堆删除不影响胜负状态。
- \(a \le x_i\le b\),只要存在这样的堆,那么取 \(a\) 的人必胜,为什么?
把所有的堆全部 \(\mod a+b\),然后把小于 \(a\) 的堆除去,然后发现剩下的可以分为 \([a,b)\),\([b+1,a+b)\),两类,对于第二类,每一堆只会被取一次,无论是 a还是 \(b\),所以 \(a,b\) 依次取,那么 \(a\) 肯定获胜。
-
\(b\le x_i < 2a\) 胜负状态和这样的堆的奇偶性有关。记这样的堆的个数为 \(M\) 个。
-
\(2a\le x_i\),存在至少 \(2\) 个这样的堆则取 \(a\) 的人必胜。存在 \(1\) 个这样的堆且 \(M\) 为偶数则先手必胜,存在 \(1\) 个且 \(M\) 为奇数则 \(a\) 必胜,不存在这样的堆且 \(M\) 为奇数则先手必胜,不存在且这样的堆且 \(M\) 为偶数则后手必胜。
一句一句分析,为什么?
首先,如果是 \(a\) 先手,那么 \(a\) 取一次直接到情况二上,此时必胜,如果是后手,\(b\) 先取一次,此时如果还有一个这样的堆,那么同样可以到状态二。
不存在这样的堆且 \(M\) 为奇数则先手必胜:情况三的堆一人取一次,先手必胜。
否则则后手必胜。
int fpow(int x,int b){
if(b<0) return 0;
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0){
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
int fpow2(int x,int b){
if(b<0) return 1;
if(x==0) return 0;
if(b==0) return 1;
int res=1;
while(b>0){
if(b&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
b>>=1;
}
return res;
}
int n,A,B,cnt[3],ans[4];
void solve(){
cin>>n>>A>>B;
int swp=0,coef_0=1;
if(A>B) swap(A,B),swp=1;
while(n--){
int x;
cin>>x;
x%=(A+B);
if(x<A) coef_0=2LL*coef_0%mod;
else if(x<B) cnt[0]++;
else if(x<2*A) cnt[1]++;
else cnt[2]++;
}
ans[0]=(fpow(2,cnt[0])-1)*fpow(2,cnt[1]+cnt[2])%mod;
ans[0]=(ans[0]+cnt[2]*fpow(2,cnt[1]-1))%mod;
ans[0]=(ans[0]+(fpow(2,cnt[2])-1-cnt[2])*fpow(2,cnt[1]))%mod;
ans[2]=fpow(2,cnt[1]-1);
ans[2]=(ans[2]+fpow2(2,cnt[1]-1)*cnt[2])%mod;
ans[3]=fpow2(2,cnt[1]-1);
if(swp) swap(ans[0],ans[1]);
for(int i=0;i<4;i++) cout<<1LL*ans[i]*coef_0%mod<<" ";
cout<<"\n";
}
放张图: