2022.10.26 总结
CF1742G
考虑拆位,先把高位的填成 1 ,后面再考虑填上低位的。
把每一位能填的数存进数组里。
从高位往低位填,每一位填时,尽量把低位也顺便填上。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,logm=32;
int n,a[N],t,res[N],w[logm],vis[N];
struct node {
int id,v;
};
bool cmp(node x,node y) {
return x.v>y.v;
}
vector<node> v[logm];
int main() {
scanf("%d",&t);
for(; t; t--) {
for(int i=logm-1; i>=0; i--) v[i].clear();
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
for(int j=0; j<logm; j++) {
if(a[i]&(1<<j)) {
node f;
f.id=i; f.v=a[i];
v[j].push_back(f);
}
}
}
memset(w,0,sizeof(w));
int tot=0;
for(int i=logm-1; i>=0; i--) {
if(w[i]) continue;
int q=0;
for(int k=0; k<logm; k++) if(!w[k]) q|=1<<k;
for(int j=0; j<v[i].size(); j++)
v[i][j].v&=q;
sort(v[i].begin(),v[i].end(),cmp);
if(v[i].size()) {
res[++tot]=v[i][0].id;
for(int j=0; j<logm; j++) {
if(a[res[tot]]&(1<<j)) w[j]=1;
}
}
}
memset(vis,0,sizeof(vis));
for(int i=1; i<=tot; i++) {
printf("%d ",a[res[i]]);
vis[res[i]]=1;
}
for(int i=1; i<=n; i++) {
if(!vis[i]) printf("%d ",a[i]);
}
puts("");
}
return 0;
}
CF1741E
考虑 DP,设 \(f_i\) 是以 \(i\) 结尾的序列是否合法。
若 \(f_{i-1}\) 合法,则 \(f_{i+a_i}\) 合法。
若 \(f_{i-a_i-1}\) 合法,则 \(f_i\) 合法。
code
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
int g,n,a[N];
bool f[N];
int main() {
scanf("%d",&g);
for(; g; g--) {
scanf("%d",&n);
f[0]=1;
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
for(int i=1; i<=n; i++) f[i]=0;
for(int i=1; i<=n; i++) {
int p=i-a[i],q=i+a[i];
if(p>=1) f[i]|=f[p-1];
if(q<=n) f[q]|=f[i-1];
}
puts(f[n]?"yes":"no");
}
return 0;
}
CF1739D
由于看到最大值最小,考虑二分。
检验的时候,有两种办法,自上而下和自下而上。
自上而下:从上往下,如果有一个点深度超出限制,则把这个点连到根。
自下而上:从下往上,如果一个点子树深度超过限制,则把子树连到根。
事实证明,自下而上检验是正确的。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,tot,head[N],ver[2*N],nxt[2*N],fa[N],k,t;
int cnt,depth[N];
void addedge(int x,int y) {
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void DFS(int u,int h) {
depth[u]=1;
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
DFS(v,h);
depth[u]=max(depth[u],depth[v]+1);
}
if(u!=1&&fa[u]!=1&&depth[u]==h) {
depth[u]=0; cnt++;
}
}
bool check(int h) {
cnt=0;
DFS(1,h);
return cnt<=k;
}
int main() {
scanf("%d",&t);
for(; t; t--) {
scanf("%d%d",&n,&k);
for(int i=1; i<=tot; i++) nxt[i]=ver[i]=0;
for(int i=1; i<=n; i++) head[i]=0;
tot=0;
for(int i=2,x; i<=n; i++) {
scanf("%d",&fa[i]);
addedge(fa[i],i);
}
int l=0,r=n;
for(; l+1<r; ) {
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
printf("%d\n",r);
}
return 0;
}
CF1736D
显然,若将 \(01010101\) 循环交换, 则变成了 \(10101010\).
我们构造 \(a_1,a_3,a_5...a_{2n-1}\)=\(a_2,a_4,a_6...a_{2n}\).
code
// LUOGU_RID: 91283804
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
char s[N];
int n,g,b[N],tot;
int main() {
scanf("%d",&g);
for(; g; g--) {
scanf("%d",&n);
scanf("%s",s+1);
tot=0;
int cnt0=0;
for(int i=1; i<=2*n; i++)
if(s[i]=='0') cnt0++;
if(cnt0%2!=0) {
puts("-1");
} else {
int m=0;
for(int i=1; i<=2*n; i+=2) {
if(s[i]=='0'&&s[i+1]=='1') {
if(m%2==0) b[++tot]=i;
else b[++tot]=i+1;
m++;
}
if(s[i]=='1'&&s[i+1]=='0') {
if(m%2==0) b[++tot]=i+1;
else b[++tot]=i;
m++;
}
}
printf("%d ",tot);
for(int i=1; i<=tot; i++) printf("%d ",b[i]);
puts("");
for(int i=1; i<=2*n; i+=2) printf("%d ",i);
puts("");
}
}
return 0;
}
CF1735D
我们要先求出所有的三元组。
由于每两个数是可以确定出另一个数的,所以五元组一定由两个含有同一元素的三元组构成。
否则,若有两个元素相等,则构成了两个一样的三元组,不构成五元组。
\(O(n^2k)\) 求出所有三元组再匹配即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1010,K=22;
typedef long long LL;
int n,k,a[N][K];
LL ans;
map<LL,bool> mp;
int main() {
scanf("%d%d",&n,&k);
for(int i=1; i<=n; i++) {
LL h=0;
for(int j=1; j<=k; j++) {
scanf("%d",&a[i][j]);
a[i][j]++;
h=h*4+a[i][j];
}
mp[h]=1;
}
for(int i=1; i<=n; i++) {
LL cnt=0;
for(int j=1; j<=n; j++) {
if(i==j) continue;
LL h=0;
for(int p=1; p<=k; p++) {
if(a[i][p]!=a[j][p]) h=h*4+(6-a[i][p]-a[j][p]);
else h=h*4+a[i][p];
}
if(mp[h]) cnt++;
}
cnt/=2;
ans+=1ll*cnt*(cnt-1)/2;
}
printf("%lld\n",ans);
return 0;
}
CF1728D
考虑 DP,设 \(f_{l,r}\) 是 \(l-r\) 区间内的胜负情况。
1 表示 Alice 胜, 0 表示平局, -1 表示 Bob 胜。
若只剩两个元素,当两元素不同,则 Alice 必胜;反之则平局。这是因为 Alice 会选最优的。
考虑怎么更新。
\(f_{l,r}\),Alice 选左边或右边的元素,希望无论 Bob 怎么选的剩余区间值最大,
Bob 会选左边或右边使剩余区间值最小。
\(f_{l,r}=\max(\min(f_{l+1,r-1},f_{l,r-2}),\min(f_{l+1,r-1},f_{l+2,r}))\).
code
// LUOGU_RID: 91426351
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int t,n,dp[N][N];
char s[N];
int main() {
scanf("%d",&t);
for(; t; t--) {
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) dp[i][j]=0;
for(int i=1; i<n; i++) dp[i][i+1]=(s[i]==s[i+1])?0:1;
for(int len=4; len<=n; len+=2) {
for(int i=1; i+len-1<=n; i++) {
int j=i+len-1;
int f1=dp[i+2][j]; // A left B left
if(f1==0&&s[i]<s[i+1]) f1=-1;
if(f1==0&&s[i]>s[i+1]) f1=1;
int f2=dp[i+1][j-1]; // A left B right
if(f2==0&&s[i]<s[j]) f2=-1;
if(f2==0&&s[i]>s[j]) f2=1;
int f3=dp[i+1][j-1]; // A right B left
if(f3==0&&s[j]<s[i]) f3=-1;
if(f3==0&&s[j]>s[i]) f3=1;
int f4=dp[i][j-2]; // A right B right
if(f4==0&&s[j]<s[j-1]) f4=-1;
if(f4==0&&s[j]>s[j-1]) f4=1;
dp[i][j]=max(min(f1,f2),min(f3,f4));
}
}
if(dp[1][n]==1) puts("Alice");
else puts("Draw");
}
return 0;
}