于是complete|

yshpdyt

园龄:1年1个月粉丝:6关注:4

Codeforces Round 938 (Div. 3)

鲜花

卡在 D 上神奇小错误,导致 F,G 很简单没来得及写 ,这场其实不怎么难。

A

题意

一堆酸奶,买一个花费 a 元,一次性买两个花费 b 元。求总共买 n 个花费最小价格。

Sol

如果一次性买两个更划算肯定要尽量多的一次性买两个,分开买划算就一个一个买。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,a,b;
void sol(){
    cin>>n>>a>>b;
    if(a*2<b)b=a*2;
    if(n&1)cout<<(n/2)*b+a<<endl;
    else cout<<(n/2)*b<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();

    return 0;
}

B

题意

大小为 n 的累进正方形是一个 n×n 矩阵,满足

ai+1,j=ai,j+c

ai,j+1=ai,j+d

给定 n×n 个数和 c,d,判断能否构成累进正方形。

Sol

c,d 为正数,左上角肯定是最小的,找到最小的元素后,先放第一排,找 a1,i+1=a1,i+d,找不到无解。

然后竖着放,和横着类似,ai+1,j=ai,j+c,找不到无解。

具体来说开个桶,桶为空就是找不到。

最后在检查一下是否所有位置都满足即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 2005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll a[N*N];
ll f[N][N];
map<ll,ll>mp;
ll n,c,d;
void sol(){
    cin>>n>>c>>d;
    mp.clear();
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)f[i][j]=0;
    for(int i=1;i<=n*n;i++){
        cin>>a[i];
        mp[a[i]]++;
    }
    sort(a+1,a+n*n+1);
    f[1][1]=a[1];
    for(int i=1;i<n;i++){
        if(mp[f[1][i]+d]){
            mp[f[1][i]+d]--;
            f[1][i+1]=f[1][i]+d;
        }else{
            cout<<"No\n";
            return ;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<n;j++){
            if(mp[f[j][i]+c]){
                mp[f[j][i]+c]--;
                f[j+1][i]=f[j][i]+c;
            }else{
                cout<<"No\n";
                return ;
            }
        }
    }
    for(int i=2;i<=n;i++){
        for(int j=2;j<=n;j++){
            if(f[i][j]!=f[i-1][j]+c){
                cout<<"No\n";
                return ;
            }
            if(f[i][j]!=f[i][j-1]+d){
                cout<<"No\n";
                return ;
            }
        }
    }
    cout<<"Yes\n";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();

    return 0;
}

C

题意

长为 n 的序列,可以进行 k 次操作。对于奇数次操作来说,使序列最左边 1,偶数次操作,使序列最右边 1。如果一个位置操作完后为 0,那么就删去一个元素,开头右移或末尾左移。

求删除了多少个数。

Sol

now=0 表示现在是奇数次操作,now=1 表示现在是偶数次操作,考虑每次操作清零一个位置,转化问题变成求保留多少个数,让 l,r 表示当前操作的左右端点。

对于 now=0

alar,花费 2×al1 次操作,进行如下变化:

arar(al1)

kk(2×al1)

al0

ll+1

now1

ar<al,花费 2×ar 次操作,进行如下变化:

alalar

kk(2×ar)

ar0

rr1

now0

对于 now=1

aral,花费 2×ar1 次操作,进行如下变化:

alal(ar1)

kk(2×ar1)

ar0

rl1

now0

al<ar,花费 2×al 次操作,进行如下变化:

araral

kk(2×ar1)

al0

ll+1

now1

特别的,对于 l=r

花费 al,如下变化并且结束循环。:

al0

kkal

rl1

结束条件

这些操作能过进行,需要 k 大于等于操作数,如果不大于直接退出循环即可。

最后的答案是 n(rl+1)

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,k,a[N];
void sol(){
    cin>>n>>k;
    for(int i=1;i<=n;i++)cin>>a[i];
    ll l=1,r=n,now=0;
    while(l<=r&&k>0){
        if(l==r){
            if(k>=a[l])cout<<n<<endl;
            else cout<<n-1<<endl;
            return ;
        }
        if(now==0){
            if(a[l]<=a[r]){
                if(k<a[l]*2-1)break;
                k-=a[l]*2-1;
                a[r]-=a[l]-1;
                now=1;
                l++;
                continue;
            }
            if(a[l]>a[r]){
                if(k<a[r]*2)break;
                k-=a[r]*2;
                a[l]-=a[r];
                now=0;
                r--;
                continue;
            }

        }
        if(now==1){
            if(a[l]>=a[r]){
                if(k<a[r]*2-1)break;
                k-=a[r]*2-1;
                a[l]-=a[r]-1;
                now=0;
                r--;
                continue;
            }
            if(a[l]<a[r]){
                if(k<a[l]*2)break;
                k-=a[l]*2;
                a[r]-=a[l];
                now=1;
                l++;
                continue;
            }
        }
    }
    cout<<n-(r-l+1)<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();

    return 0;
}

D

题意

求长为 n 的序列 ai,有多少个长为 m 的区间,使得区间里的数经过任意排列后可以与另一个长为 m 的序列有至少 k 个位置相同。

Sol

动态维护以 i 开头长为 m 的区间。先将 bi 序列以桶的方式存下来,设为 c 数组。

类似滑动窗口,每次右移开头减去旧开头的贡献,加上新末尾的贡献。

具体来说就是开个桶 cnt,让 cntai+m1cntai+m1+1cntai1cntai11

然后对比 c 桶,若 cntai+m1cai+m1kk1,若 cntai1<cai1kk+1,若 k0 ,说明该区间合法,这样做的意义显然,即维护区间相似度。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 1000005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
map<ll,ll> mp,cnt;
ll n,m,a[N],k,res,now; 
bool vis[N];
void sol(){
    res=0,now=0;
    cin>>n>>m>>k;
    mp.clear();
    cnt.clear();
    for(int i=1;i<=n;i++){
        cin>>a[i];
        vis[i]=0;
    }
    for(int i=1;i<=m;i++){
        ll x;
        cin>>x;
        mp[x]++;
    }
    for(int i=1;i<=n;i++){
        cnt[a[i]]++;
        if(cnt[a[i]]<=mp[a[i]])now++;
        if(i<m)continue;
        if(now>=k)res++;
        cnt[a[i-m+1]]--;
        if(cnt[a[i-m+1]]<mp[a[i-m+1]])now--;
    }
    cout<<res<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();

    return 0;
}

E

题意

01 串,找到最大的 k 使得每次取反连续的 k 个数(也就是 0110),让01 串可以全变成 1

Sol

首先明确一下如何取反区间比较容易判断。从最左边开始遍历,如果当前位置是 0 就将当前位置为开头的长为 k 的区间反转,如果是 1 则不反转,这样只需要判断后面的 k1 个数是否全 1 即可。

然后根据数据范围来看算法大概是 O(n2) 的 ,由于没有单调性没法二分,枚举长度 O(n) ,尝试 O(n) 判断。

枚举所有长为 k 的区间,我们使用 fli 表示 以 i 开头的区间是否取反过,cnt 表示当前区间的取反次数。

还是类似滑动窗口的思想,动态维护 cnt

对于一次循环,依次进行如下判断:

flik1=1cntcnt1,这是因为 ik1 位置进行过一个取反操作,这次取反操作最多影响到 i1,所以取反次数要减一,但是取反其实奇偶性有关系,所以用异或代替。

cntsi=0,说明需要对以 i 开头的区间取反,让 cntcnt1 ,以及fli1

两个判断对 i 有一定范围限制。

取最大的 k 即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 800005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
map<ll,ll> mp;
ll n,fl[N];
bool vis[N];
string s,t;
void sol(){
    cin>>n>>s;
    s=" "+s;
    
   
    for(int p=n;p>=1;p--){
        ll cnt=0;
        for(int i=1;i<=n;i++)vis[i]=0;
        int i=1;
        for(i=1;i+p-1<=n;i++){
            ll t=s[i]-'0';
            if(t^cnt==0){
                cnt^=1;
                vis[i]=1;
            }
            if(i>=p&&vis[i-p+1])cnt^=1;
        } 
        ll fl=0;
        for(;i<=n;i++){
            ll t=s[i]-'0';
            if(t^cnt==0){
                fl=1;
                break;
            }
            if(i>=p&&vis[i-p+1])cnt^=1;
        }
        if(fl==0){
            cout<<p<<endl;
            return ;
        }
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout); 
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();

    return 0;
}

F

题意

n=a1+a2+a3+a4 场比赛,初始有 a1 枚面值为 1 的硬币,a2 枚面值为 2 的硬币,a3 枚面值为 3 的硬币,a4 枚面值为 4 的硬币,每场比赛结束后移去一枚银币,直到没有硬币。如果所有银币面值异或起来为 0 就获胜,你可以改变每场移去硬币的顺序,求最大的获胜场数。

Sol

本题的关键在于凑偶数。

注意到面值为 4 的银币比较特殊,其异或其他面额的硬币任意枚的结果都不为 0,所以我们可以先让 4 的数量为偶数,把其他三类移空后再移去 4,这样会对答案贡献:

a42

然后默认 4 不存在,考虑其它三类,由于 123=0 ,如果 a1,a2,a3 均是奇数,那么可以算一次贡献。然后让他们全部变成偶数,也就是除了全奇数的情况需要加一次贡献,其余直接舍弃掉减一。接下来可以让三个位置都减一,三场比赛赢一场,或者让其中一个位置减二,两场比赛硬一场,显然后面的更优也更可持续发展,因为我们无法保证三个位置的数相同,总会有一个位置先减完,不如挨个减,然后就和 4 的情况类似,所以答案为:

res=a12+a22+a32+a42+[a1mod2+a2mod2+a3mod2==3]

Code

#include<bits/stdc++.h>
#define ll long long
#define N 800005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll a[7];
void sol(){
    cin>>a[1]>>a[2]>>a[3]>>a[4];
    ll res=a[4]/2;
    res+=a[1]/2+a[2]/2+a[3]/2;
    if((a[1]&1)&&(a[2]&1)&&(a[3]&1))res++;
    cout<<res<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout); 
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();

    return 0;
}

G

题意

n×m 网格,最大化从 (1,1)(n,m) 路径上的值的最大公约数。

Sol

w=gcd(a1,1,an,m),那么答案肯定是 w 的因数,求出其所有因数然后 bfs,记 w0(modp),答案合法当且仅当路径上所有点 ai,j0(modp)

得卡卡常,不然被 hack 得很惨。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 205
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m;
ll a[N][N];
bool vis[N*N];
ll w;
queue<pair<ll,ll> >q;
bool bfs(ll x,ll y,ll p){
    while(!q.empty())q.pop();
    q.push({x,y});
    vis[(x-1)*m+y]=1;
    while(!q.empty()){
        auto t=q.front();
        q.pop();
        vis[(t.fi-1)*m+t.se]=0;
        if(t.fi==n&&t.se==m)return 1;
        for(int i=1;i<=2;i++){
            ll nx=t.fi+(i==1);
            ll ny=t.se+(i==2);
            if(nx>n)continue;
            if(ny>m)continue;
            if(vis[(nx-1)*m+ny]||a[nx][ny]%p)continue;
            q.push({nx,ny});
            vis[(nx-1)*m+ny]=1;
        }
    }
    return 0;
}
vector<ll>v; 
bool cmp(ll x,ll y){
    return x>y;
}
void sol(){
    v.clear();
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    w=__gcd(a[1][1],a[n][m]);
    int i=1;
    for(i=1;i*i<w;i++){
        if(w%i==0){
            v.push_back(w/i);
            v.push_back(i);
        }
    }
    if(i*i==w)v.push_back(i);
    sort(v.begin(),v.end(),cmp);
    for(auto i:v){
        if(bfs(1,1,i)){
            cout<<i<<endl;
            return ;
        }
    }

}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();
    return 0;
}

H

题意

n×m 的网格,k 个塔,一条从 (1,1)(n,m) 敌人移动的路径 ,路径上的每个位置都会恰好经过一次。敌人会水平或垂直移动到相邻的单元格,每秒移动一次。

每秒防御塔会对其范围内的所有敌人造成 pi 的伤害,默认所有塔的攻击范围都为零,但可以将塔的攻击范围设为 r,并且使所有敌人的生命值增加 3r 。每一座攻击范围不为 0 的塔的 r 都不能相同

定义攻击范围内的点需要满足的条件是:(xxi)2+(yyi)2r2(xi,yi) 是塔的坐标。

敌人的基础生命为 h ,找出最大的 h 使得敌人到达 (n,m) 时的血量 小于等于零

Sol

注意到虽然敌人不只有一个,但是所有敌人的受攻击情况是相同的,所以只对一个敌人分析。

我们可以在最坏 O(knm) 的复杂度内求出每个塔攻击范围为 r 时,可以对一个敌人造成的伤害,具体来说就是攻击范围内覆盖敌人的路径长度乘攻击伤害。

具体来说,我们将所有路径上的点保存下来,然后枚举塔和路径上的点,令 g[i][r] 表示对攻击范围在 (r1,r] 的敌人路径造成的总伤害:

g[i][(xxi)2+(yyi)2]g[i][(xxi)2+(yyi)2]+pi

然后 f[i][r] 表示攻击范围为 r 时对路径上的敌人造成的总伤害,显然是求个前缀和。

f[i][r]=j=0rg[i][j]

但是我们还要对敌人血量增加 3r ,所以造成的贡献是:

dp[i][r]=3rf[i][r]

然后这玩意要和 h 联系起来。

h+i=1kdp[i][ri]0

其中 ri 互不相同,不妨改变一下,变成:

dp[i][r]=f[i][r]3r

hi=1kdp[i][ri]

然后就是求右边那坨的最大值,特别的注意到如果起副作用,也就是小于零,不如让其攻击距离为零,所以要保证每个位置非负。

然后问题就变成了 P4014 分配问题

但是不会费用流,所以剩下的部分没写,详情参照如上题目题解。

Code

提供一下求 dp[i][r] 的代码 。

#include<bits/stdc++.h>
#define ll long long
#define x1 xx
#define y1 yy
#define dl double
#define N 55
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m,k;
struct pt{
   ll x,y;
};
ll a[N][N],f[N*N][30];
ll upz(pt i,pt j){
   dl x1=i.x;
   dl x2=j.x;
   dl y1=i.y;
   dl y2=j.y;
   dl res1=sqrt(((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)));
   ll res2=ceil(res1);
   return res2;
}
vector<pt>v;
ll res=0;
void work(ll x,ll y,ll z,ll id){
   ll mx=0;
   for(auto i:v){
       ll t=upz({x,y},i);
       mx=max(t,mx);
       f[id][t]+=z;
   }
   for(int i=1;i<=mx;i++){
       f[id][i]+=f[id][i-1];
   }
   ll x3=1;
   for(int i=0;i<=mx;i++){
       f[id][i]=max(f[id][i]-x3,0ll);
       cout<<f[id][i]<<" ";
       x3*=3;
   }
   cout<<endl;
}
void sol(){
   res=0;
   v.clear();
   cin>>n>>m>>k;
   for(int i=1;i<=k;i++)for(int j=0;j<=25;j++)f[i][j]=0;
   for(int i=1;i<=n;i++){
       string s;
       cin>>s;
       s=" "+s;
       for(int j=1;j<=m;j++){
           a[i][j]=(s[j]=='#');
           if(s[j]=='#')v.push_back({i,j});
       }
   }
   for(int i=1;i<=k;i++){
       ll x,y,z;
       cin>>x>>y>>z;
       work(x,y,z,i);
   }
   m=0;
   cout<<endl;
}
int main(){
   ll ttt;
   cin>>ttt;
   while(ttt--)sol();
   return 0;
}

本文作者:yshpdyt

本文链接:https://www.cnblogs.com/yshpdyt/p/18123023

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   yshpdyt  阅读(187)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起