一.闲话
最近准备省选,好久没写题解了qwq,今天的比赛挺有意思的,就来写几道题吧,qwq
二.题解
G.Mathematical Modelling Class
这道题只要读懂题其实挺简单的。但是貌似没几个人读然后被我这个菜鸡拿了一血(大雾)
题目大意:
有n个人,将每个人划分进A,B两组中的一组,第i个人划分进A组后,对A组的贡献为ai;划分进B组后,对B组的贡献为bi(ai,bi={0,1}),求是否存在一种方案,使得A组划分进了个人,B组划分进剩下的人,且A,B组的总贡献一样。若存在输出任意一种划分方式,若不存在输出-1。
读题后,我们发现,其实,一共只有四种人,即:
1.ai=0,bi=0
2.ai=0,bi=1
3.ai=1,bi=0
4.ai=1,bi=1
那么,我们只需要分别统计下这四种人分别有几个,再来看怎么划分,题目就会简单许多。
设第i种人有s[i]个,m=
那么,我们考虑枚举最终两组的总贡献,设为S。
那么,我们就有,对于A组的人来说,一定有:
A组中,第一种人的个数+第二种人的个数=m-S,第三种人的个数+第四种人的个数=S
这样,我们只需要找到一种方案数,使得A组满足上述条件,并且剩下的人对B组的贡献也等于S即可
那么,我们只要分别枚举下第一/二种人和第三/四种人的个数,就可以知道A组中所有类型的人的数量了,然后我们再判断下是否成立,就可以了。
但是,如果我们同时枚举第一/二种人和第三/四种人的个数的话,复杂度就会上升至,再加上一开始的枚举最终总贡献,复杂度就变成了
,显然不可行
那么,该怎么办呢?
一个简单的办法,我们参考折半搜索。先枚举第一/二种人的数量,然后记录下当前数量下第一/二种人对B组的总贡献,然后,我们再枚举第三/四种人的数量,算出当前数量下对B的总贡献,判断是否存在两个贡献和为S的情况即可。
复杂度
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=5001;
bool dp[N];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,T=n,m;
scanf("%d",&n);
m=n/2+(n&1);
string x,y;
cin>>x>>y;
int one=0,two=0,thr=0,fou=0;
for(int i=0;i<n;++i){
if(x[i]=='0'&&y[i]=='0')++one;
if(x[i]=='0'&&y[i]=='1')++two;
if(x[i]=='1'&&y[i]=='0')++thr;
if(x[i]=='1'&&y[i]=='1')++fou;
}
int a=-1,b=-1,c=-1,d=-1;bool flag=0;
for(int i=0;i<=m;++i){//上午班学生1的个数
int l=m-i,r=i;//one和two一共选l个,thr和fou一共选r个
//两边分别枚举
memset(dp,0,sizeof(dp));
for(int j=min(l,one);~j;--j){//枚举选j个one
if(l-j>two)break;
dp[two-l+j]=1;
}
for(int j=min(r,thr);~j;--j){//枚举选j个thr
if(r-j>fou)break;
int T=i-(fou-r+j);
if(T>=0&&dp[T]){
flag=1;
c=j,d=r-j;b=two-T,a=l-b;
break;
}
}
if(flag)break;
}
if(!flag){
puts("-1");
continue;
}
for(int i=0;i<n;++i){
if(x[i]=='0'&&y[i]=='0'&&a){
--a;
printf("%d ",i+1);
}
if(x[i]=='0'&&y[i]=='1'&&b){
--b;
printf("%d ",i+1);
}
if(x[i]=='1'&&y[i]=='0'&&c){
--c;
printf("%d ",i+1);
}
if(x[i]=='1'&&y[i]=='1'&&d){
--d;
printf("%d ",i+1);
}
}
puts("");
}
return 0;
} L.Yet Another Bracket Sequence
注意到,一个括号匹配序列是否合法的充要条件是:
对于任意一个前缀,'('的个数大于等于')',且对于整体,'('的个数等于')'
那么,我们将题目变为维护每个前缀中'('的个数-')'的个数的值。
如果我们维护出来了,只有所有前缀中的值最小的那个>=0,且最后的前缀的值为0,那么这就是一个合法的括号匹配了。
如果我们把第x个括号改变了的话,那么,相当于把x-n的前缀的值+2/-2,那么,用线段树维护区间修改+区间最值即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1;
char ch[N];int W[N<<2],laz[N<<2];
int s[N];
inline void down(int now){
W[now<<1]+=laz[now],W[now<<1|1]+=laz[now];
laz[now<<1]+=laz[now],laz[now<<1|1]+=laz[now];
laz[now]=0;
}
inline void update(int now){
W[now]=min(W[now<<1],W[now<<1|1]);
}
inline void build(int now,int l,int r){
if(l==r){
W[now]=s[l];
return;
}
int mid=(l+r)>>1;
build(now<<1,l,mid),build(now<<1|1,mid+1,r);
update(now);
}
inline void insert(int now,int l,int r,int lc,int rc,int x){
if(lc<=l&&r<=rc){
W[now]+=x,laz[now]+=x;
return;
}
down(now);
int mid=(l+r)>>1;
if(lc<=mid)insert(now<<1,l,mid,lc,rc,x);
if(rc>mid)insert(now<<1|1,mid+1,r,lc,rc,x);
update(now);
}
inline int find(int now,int l,int r,int x){
if(l==r){
return W[now];
}
down(now);
int mid=(l+r)>>1;
if(x<=mid)return find(now<<1,l,mid,x);
return find(now<<1|1,mid+1,r,x);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
scanf("%s",ch+1);
for(int i=1;i<=n;++i){
int val;
if(ch[i]=='(')val=1;
else val=-1;
s[i]=s[i-1]+val;
}
build(1,1,n);
while(m--){
int x;
scanf("%d",&x);
if(ch[x]=='('){
ch[x]=')';
insert(1,1,n,x,n,-2);
}else{
ch[x]='(';
insert(1,1,n,x,n,2);
}
if(W[1]==0&&find(1,1,n,n)==0){
puts("Yes");
}else{
puts("No");
}
}
return 0;
} M.Yet Another Stones Game
这题用SG函数推的。。。
n=2大家都会做就不多说了。
当n=4时,
我们假设有两个数,l=r,
那么,我们明显有如果我们拿这个点对(l,r)
当l=0时
sg(l,r)=0 [无法拿,必败]
当l=1时
sg(l,r)=1(算mex即可)
当l=2时
sg(l,r)=2
...
于是,我们有sg(l,r)=l
更一般的,对于操作点对(a1,a2...ak)我们不难得出
sg(a1,a2...ak)=min(a1,a2...ak)
那么,根据nim游戏的性质,因为每次我们取n/2个点,那么如果我们确定了取的n/2个点,整个游戏的sg值即为
有nim游戏的结论可知,若整个局面的sg值为0,则先手必败,否则先手必胜,那么我们只要判断下是否存在一种情况使得整个局面sg值不为0即可。
又因为两个部分的sg值是各个部分的最小值,所以,最佳的划分方法是,我们将最小的n/2的数划为一组,剩下的划为另一组,然后,我们判断他们的sg是否相等即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int a[51];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
if(a[n/2+1]!=a[1]){
puts("Happy Little Gyro");
}else{
puts("Sad Little Gyro");
}
}
return 0;
} 
京公网安备 11010502036488号