Codeforces Round #683 (Div. 2, by Meet IT)
A,B过水。
C:给20w个数,给你一个数K,问你能不能在这20w个数里,找到若干个数使得它们的和超过ceil[K/2],且不超过K。
题解:如果20w个数中,有一个满足的,那直接白给。否则,在小于ceil[k/2]的数中,你直接往上加,如果满足,就输出。加不够则不满足。你可以一个一个加的原因就在于,两个小于ceil(K/2)的数加起来是不会超过K的。我写的时候,加了个排序,属实没必要。
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
#define fi first
#define se second
typedef pair<int,int> PII;
typedef long long LL;
int n;
LL w;
PII a[N];
int main(){
int _;cin>>_;
while(_--){
cin>>n>>w;
LL s=0;
for(int i=1;i<=n;++i){
int x;cin>>x;
s+=x;
a[i]=make_pair(x,i);
}
sort(a+1,a+n+1);
if(s<(1+w)/2){
cout<<"-1"<<endl;
continue;
}else{
int idx=n;
while(idx>0&&a[idx].fi>w)--idx;
vector<int> v;
s=0;
for(int i=idx;i>=1;--i){
s+=a[i].fi;
v.push_back(a[i].se);
if(s>=(w+1)/2)break;
}
if(s<(w+1)/2){
cout<<"-1"<<endl;
continue;
}
cout<<v.size()<<endl;
for(auto& x:v){
cout<<x<<" ";
}
cout<<endl;
}
}
return 0;
}
D:给你两个字符串,你可以选取这两个字符串的字串。并且计算它们的分数,分数的定义是,最长公共子序列*4-两个子串的长。
题解:定义\(dp[i][j]\)为,以i结尾的a串,以j结尾的b串所对应的最大分数。其实它的转移是比较显然的。a[i]==b[j]时,\(dp[i][j]=2+max(dp[i-1][j-1],0)\)。否则,\(dp[i][j]=max(-2,dp[i-1][j-1]-2)\)。当然,对于所有情况\(dp[i][j]=max(dp[i-1][j],dp[i][j-1])-1\)的转移都是成立的。
#include<bits/stdc++.h>
using namespace std;
const int N = 5005;
char a[N], b[N];
int dp[N][N];
int n, m;
void chmax(int& x,int y){
if(y>x)x=y;
}
int main(){
cin>>n>>m;
cin>>a+1>>b+1;
int ans=0;
memset(dp,-0x3f,sizeof dp);
dp[0][0]=0;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(a[i]==b[j]){
chmax(dp[i][j],dp[i-1][j-1]+2);
chmax(dp[i][j],2);
}else{
chmax(dp[i][j],dp[i-1][j-1]-2);
chmax(dp[i][j],-2);
}
chmax(dp[i][j],dp[i-1][j]-1);
chmax(dp[i][j],dp[i][j-1]-1);
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
chmax(ans,dp[i][j]);
}
}
cout<<ans;
return 0;
}
E: 给你n个不同的数,每一个数会与和自己xor值最小的数连边,重边算一条。问你最少删除多少个数字,使得它们形成的无向图是一棵树。
题解:建立01trie,从高位向低位考虑,如果某一个节点的两颗子树size都大于2的话,那显然是不行的。因为它们肯定会选择自己子树中的数字组成最小的xor值。再考虑到,由于n个数中,存在一个客观上的最小xor值,所以必有两个数互相连。这说明我们的图,最多只有n-1条边。此时想成为一棵树,只需要这个无向图是联通的就可以了。如果我们让01trie任意一层的子树,不存在两个子树size都大于2。那么就可行了。因为每次剩下最多一个(选择其中一个子树,使其size为1),会连向其他的连通块,这样会构成一种合法的方案。
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n;
int ans;
int trie[N<<5][2], cntNode, cnt[N<<5];
void Insert(int x){
int cur=0;
for(int i=30;i>=0;--i){
if(!trie[cur][x>>i&1]){
trie[cur][x>>i&1]=++cntNode;
}
cur=trie[cur][x>>i&1];
++cnt[cur];
}
}
void dfs2(int nd, int dep, int cst){
if(dep==30){
ans=min(ans,cst);
return;
}
if(!trie[nd][0]){
dfs2(trie[nd][1],dep+1,cst);
}else if(!trie[nd][1]){
dfs2(trie[nd][0],dep+1,cst);
}else{
dfs2(trie[nd][0],dep+1,cst+cnt[trie[nd][1]]-1);
dfs2(trie[nd][1],dep+1,cst+cnt[trie[nd][0]]-1);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
int x;scanf("%d",&x);
Insert(x);
}
ans=n;
dfs2(0,0,0);
printf("%d",ans);
return 0;
}
F: 给你n个数,你需要找到一个最长的区间,满足的条件是里面的众数不止一个。
题解:首先有一个观察,对于这样的一个最大区间,区间众数肯定是在其中的(如果整个区间的区间众数不止一个,那直接白给),这个比较显然,考虑答案区间有一个替换更优的性质。对于easy version,这个时候已经可以开始枚举是哪个数了,因为不同的数最多100个。但是这个方法不足以解决hard version。
考虑出现次数比较多的数字,比如出现次数大于sqrt(n)的数字。可以直接考虑枚举这个数,可以发现这样的数不超过sqrt(n)个。
对于出现次数比较少的数字,比如出现次数小于sqrt(n)的数字。由于它要成为区间众数,所以出现次数也不会特别多,上限是sqrt(n)次。所以我们直接枚举出现次数,尺取答案就好。
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, mx, big, a[N], cnt[N], lst[N<<1], processed[N];
int solve1(int num){
memset(lst,-1,sizeof lst);
int res=0;
int now=0;
lst[now+n]=0;
for(int i=1;i<=n;++i){
if(a[i]==big)++now;
if(a[i]==num)--now;
if(lst[now+n]!=-1){
res=max(res,i-lst[now+n]);
}
if(lst[n+now]==-1){
lst[n+now]=i;
}
}
return res;
}
int solve2(int limit){
int res=0;
int eql=0;
memset(cnt,0,sizeof cnt);
for(int i=1,j=1;i<=n;++i){
++cnt[a[i]];
if(cnt[a[i]]==limit)++eql;
while(j<=i&&cnt[a[i]]>limit){
--cnt[a[j]];
if(cnt[a[j]]==limit-1)--eql;
++j;
}
if(eql>=2){
res=max(res,i-j+1);
}
}
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",a+i);
++cnt[a[i]];
if(cnt[a[i]]>mx){
mx=cnt[a[i]];
big=a[i];
}
}
int ans=0;
int up=sqrt(n);
for(int i=1;i<=n;++i){
if(cnt[a[i]]>=up&&a[i]!=big&&!processed[a[i]]){
processed[a[i]]=1;
ans=max(ans,solve1(a[i]));
}
}
for(int i=1;i<up;++i)ans=max(ans,solve2(i));
cout<<ans;
return 0;
}