ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
题目链接:http://codeforces.com/contest/1077
官方题解:https://codeforces.com/blog/entry/63274
A题
简单的奇偶问题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll t,a,b,k;
int main(){
scanf("%I64d",&t);
while(t--){
scanf("%I64d%I64d%I64d",&a,&b,&k);
ll ans =0;
if(k&1){
ans += a*(k/2+1)-b*(k/2);
}else{
ans += (a-b)*(k/2);
}
cout<<ans<<endl;
}
return 0;
}
B题
求最少关掉几栈灯等使得大家互不打扰,开关问题
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
int n,a[105];
int main(){
cin>>n;
fo(i,1,n)cin>>a[i];
int ans = 0;
fo(i,3,n){
if(a[i-2]==1&&a[i-1]==0&&a[i]==1){
ans++;
a[i]=0;
}
}
cout<<ans;
}
C题
题意:删掉序列中的一个数,使得这个数列成为Good array(有一个数等于其余的数的和)
解法:删掉一个数变成good array
只有两种删法,排序后,删掉最后一个数,然后查看前n-2个数和是否等于a[n-1]
删掉中间的某个数,查看剩余数的和是否等于最后一个数
删掉的数为sum[n-1] - a[n], 计算该数有多少个就行(二分一下或者枚举)
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
int read()
{
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
struct node{
int val,idx;
bool operator < (const node &a)const{
return val<a.val;
}
}a[200005];
int n;
ll sum[200005]; // 千万注意 int 1e10就gg了
vector<int> ver;
int my_upper_bound(int x){
int L=1,R=n-1,mid,ans=n; // ans设置比上界大一
while(L<=R){
mid=(L+R)>>1;
if(a[mid].val>x){
ans = mid;
R = mid-1;
}else{
L = mid+1;
}
}
return ans;
}
int my_lower_bound(int x){
int L=1,R=n-1,mid,ans=n;// ans设置比上界大一
while(L<=R){
mid=(L+R)>>1;
if(a[mid].val>=x){
ans = mid;
R = mid-1;
}else{
L = mid+1;
}
}
return ans;
}
void solve(){
fo(i,1,n){
sum[i] = sum[i-1]+a[i].val;
}
// 删掉非最后一个数,使得和等于最后一个数
ll t = sum[n-1] - a[n].val;
if(t>0&&t<=a[n].val){ // 这个t一定要注意!!!要在查找范围内
int t2 = my_upper_bound(t);
int t1 = my_lower_bound(t);
fo(i,t1,t2-1){
ver.push_back(a[i].idx);
}
// fo(i,1,n-1)if(a[i].val==t)ver.push_back(a[i].idx);
}
if(a[n-1].val==sum[n-2])ver.push_back(a[n].idx); // 删掉最后一个数
printf("%d\n",ver.size());
for(int i=0; i<ver.size(); i++){
printf("%d%c",ver[i],i==ver.size()-1?'\n':' ');
}
}
int main(){
scanf("%d",&n);
fo(i,1,n){
// scanf("%d",&a[i].val);
a[i].val = read();
a[i].idx=i;
}
sort(a+1,a+1+n);
if(n>2)solve();
else puts("0");
return 0;
}
C题别人做法
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,a[200005];
map<int,int> mp; // 真的变慢
vector<int> ver;
ll sum;
int main(){
scanf("%d",&n);
fo(i,1,n)scanf("%d",&a[i]),sum+=a[i],mp[a[i]]++;
ll t;
fo(i,1,n){
t = sum - a[i];// 删掉一个数
if(t&1)continue;
t>>=1; // 一半
if(t>0&&t<=1000000){ // 在合法范围内
// if((t!=a[i]&&mp[t]>=1) || (t==a[i]&&mp[t]>=2)){
if(t!=a[i]&&mp[t]>=1 || t==a[i]&&mp[t]>=2){ // 找一个数等于一半的,注意可能刚好一半等于删掉的那个数(则至少需要两个)
ver.push_back(i);
}
}
}
printf("%d\n",ver.size());
for(int i:ver)printf("%d ",i);
return 0;
}
D题
题意:n个数找k个数,要求这k个数出现的次数最少为times求times的最大值
解法:二分答案,即二分cut time 找到最大的cut time 更新答案
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
struct node{
int val,idx;
// 按出现次数排序
bool operator < (const node &a)const{
if(a.idx!=idx)return idx<a.idx;
else{
return val>a.val; // 出现次数相同按值排序
}
}
}a[200005];
int n,k,mx=-1;
map<int,int> mp;
vector<int> ans;
bool ok(int mid){ // 重复次数
// 计算重复段(可剪次数)大于等于mid有几个数(包含重复数字)
// 例如: mid=2。 1,1,1,1,1,1 有3个1满足
int t = 0,last = -1,loo=1;//重复次数
for(int i=n; i>=1;){
if(a[i].val==last)loo++; // 和上一个重复了
else loo=1,last=-1; // 新的数出现
if(a[i].idx>=mid*loo){
last = a[i].val;
t++;
i-=mid;
}else i--;
}
return t>=k; // 是否有大于等于k个字节
}
// 获取答案,和ok函数差不多
void get(int mid){
int t = 0,last = -1,loo=1;
for(int i=n; i>=1;){
if(a[i].val==last)loo++;
else loo=1,last=-1;
if(a[i].idx>=mid*loo){
last = a[i].val;
ans.push_back(a[i].val);
t++;
i-=mid;
}else i--;
if(t==k)break;
}
}
void solve(){
int L=1,R=mx,mid,ANS=1;
// 二分最大重复段
while(L<=R){
mid = (L+R)>>1;
if(ok(mid)){
L = mid+1;
ANS = mid;
}else R = mid-1;
}
get(ANS);
int t = ans.size();
for(int i=0;i<t; i++){
printf("%d%c",ans[i],i==t-1?'\n':' ');
}
}
int main(){
cin>>n>>k;
fo(i,1,n){
scanf("%d",&a[i].val);
mp[a[i].val]++;
}
fo(i,1,n){
a[i].idx = mp[a[i].val]; // 出现次数
mx = max(mx,a[i].idx);
}
sort(a+1,a+1+n); // 先对次数排序
// for(int i=1;i<=n; i++){
// printf("%d%c",a[i].val,i==n?'\n':' ');
// }
solve();
}
D题其他解法
#include<bits/stdc++.h>
using namespace std;
int b[300000];
int a[300000];
int main()
{
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++){
int x;
cin>>x;
b[x]++;
}
priority_queue<pair<int,int> >pq,res;
for(int i=0;i<300000;i++)
{
if(b[i])pq.push({b[i],i});
}
for(int i=0;i<k;i++)
{
cout<<pq.top().second<<" "; //大根堆,堆顶必是答案
pair<int,int>h=pq.top();
pq.pop();
a[h.second]++;
h.first=b[h.second]/(a[h.second]+1); // 要出现 a[h.second]+1次的话每个数只能被cutb[h.second]/(a[h.second]+1)次
pq.push(h);// 剩下的次数,进入排序了
}
}
E题
题意:一堆数中找到和最大的倍增序列
解法:我们可以很容易知道,这题的数值是没有作用的,因为每个数只能在在以个contenes中使用,所以我们只用考虑每个数出现的次数,得到一个序列
我一开始的做法
按次数排序 ,然后对值去重,
枚举每个每个数做为起点选择话题,再枚举1~这个数的出现次数,做为倍增序列 的第一个起点数
然后算。。。。然后T了
上面的思想是从最小的开始枚举起
我们可以做一步优化,从最大的递推算出起点 val[i] = min(val[i+1]/2, ver[i]) ,最后面的val设置为最大
也就是说,我们每一步都可以知道倍增序列的起点,和长度,
长度为i,起点为val[i]的倍增序列的和为
a[i] * ((1<<i)-1)
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
const int maxn = 2e5+5;
int n;
map<int,int>mp;
vector<int> ver;
int val[maxn];
int calc(){
n = ver.size();
int ans = val[n-1] = ver[n-1]; // 取最大次数做为第一次比赛,仅此一次比赛
for(int i=n-2; i>=0; i--){
// val[i]作为第一次比赛
val[i] = min(val[i+1]/2, ver[i]); // 保证val[i] 与后面的数可以形成 val[i],2*val[i],4*val[i]...这样的序列
if(val[i]==0) break; // 不会再更新答案了
// val[i],val[i+1]...val[n-1]共 n-i个数, 1+2+4+8... = 2^(n-i)-1
int t = val[i] * ((1<<(n-i))-1);
ans = max(ans,t);
}
return ans;
}
int main(){
cin>>n;int a;
// 值去重,次数排序
fo(i,1,n){
scanf("%d",&a);
mp[a]++; // 去重
}
for(auto it : mp) ver.push_back(it.second);
sort(ver.begin(), ver.end());
cout<<calc();
}
F1题
题意:在n个数里选x个数,并且这n个数中,每k个数至少要有一个数被选中,求选中的x个数和最大
解法:DP! 连续不超过k的区间转移到下一个点
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示选了i个选到了第j个的最大价值
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]从
d
p
[
i
−
1
]
dp[i-1]
dp[i−1][前面的k区间] 转移过来
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
h
]
)
,
h
=
[
m
a
x
(
0
,
j
−
k
)
,
j
−
1
]
dp[i][j] = max(dp[i-1][h]),h=[max(0,j-k),j-1]
dp[i][j]=max(dp[i−1][h]),h=[max(0,j−k),j−1]
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register ll i=j; i<=n; ++i)
using namespace std;
ll n,k,x,a[205],dp[205][205];
void solve(){
memset(dp,-1,sizeof(dp));
dp[0][0]=0;
fo(i,1,x){ // 选了i个
fo(j,i,n){ // 选到第j个
for(ll h=max(0ll,j-k); h<=j-1; h++){ // 可转移的区间h, dp[i-1][h]+a[j]->dp[i][j]
if(dp[i-1][h]!=-1) dp[i][j] = max(dp[i][j], dp[i-1][h]+a[j]);
}
}
}
ll mx = -1;
// 最后[n-k+1,n]之间至少要有一个数被挑选到才能是一个合格的序列
for(int i=n-k+1; i<=n; i++)mx = max(mx, dp[x][i]);
cout<<mx;
}
int main(){
scanf("%lld%lld%lld",&n,&k,&x);
fo(i,1,n)scanf("%lld",&a[i]);
solve();
return 0;
}
F2题
题意:上面的F1题数据量加大,需要O(nx)时间复杂度的算法
解法:主要优化这一步:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
h
]
)
,
h
=
[
m
a
x
(
0
,
j
−
k
)
,
j
−
1
]
dp[i][j] = max(dp[i-1][h]),h=[max(0,j-k),j-1]
dp[i][j]=max(dp[i−1][h]),h=[max(0,j−k),j−1],
使其能在O(1)时间内算出。距离做法使用单调递减列队优化。
每次取队头就行
f
[
i
]
[
i
]
f[i][i]
f[i][i] 从
f
[
i
−
1
]
[
i
−
1
]
f[i-1][i-1]
f[i−1][i−1]转移过来最优,因为同样是选了
i
−
1
i-1
i−1个,
j
=
i
−
1
j=i-1
j=i−1的时候选择最多也最优
f
[
i
]
[
j
]
f[i][j]
f[i][j]从
f
[
i
−
1
]
[
i
−
1
]
到
f
[
i
−
1
]
[
j
−
1
]
f[i-1][i-1] 到 f[i-1][j-1]
f[i−1][i−1]到f[i−1][j−1]转移过来,
[
i
−
1
,
j
−
1
]
[i-1,j-1]
[i−1,j−1]在k区间合法范围内
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int maxn = 5005;
int n,k,x,a[maxn],q[maxn];
ll f[maxn][maxn];
void solve(){
fo(i,1,n)f[0][i] = -1e15;
int l,r;
fo(i,1,x){
// f[i][i] 从 f[i-1][i-1]转移过来最优
// f[i][j] 从 f[i-1][i-1] 到 f[i-1][j-1]转移过来,[i-1,j-1]在k区间合法范围内
q[l=r=1] = i-1;// 一个新的单调递增减列队,使用闭区间
fo(j,i,n){
while(l<=r && q[l]<j-k) l++; // 删除无用位置,左区间超过k区间[j-k,j-1]范围
if(l<=r)f[i][j] = f[i-1][q[l]] + a[j];
while(l<=r && f[i-1][q[r]]<=f[i-1][j]) r--;// 删除无用的队尾
q[++r] = j; // 存入新的比较好的下标
}
}
ll mx = -1;
// 可能的答案区间1
for(int i=n-k+1; i<=n; i++) mx = max(mx, f[x][i]);
cout<<mx;
}
int main(){
cin>>n>>k>>x;
fo(i,1,n)scanf("%I64d",&a[i]);
solve();
return 0;
}