2022 CCPC湖北省赛

2022 CCPC湖北省赛

​ 这场打的怎么说,很难受。过年来与几个亲戚家的孩子见了面,被灌了不少白酒,没感觉什么酱香有啥好喝的,脑子倒是快成浆糊了。怒了,加训。题解里签到题的做法会写的简单点,这个[每日一棵splay](2022 Hubei Provincial Collegiate Programming Contest 题解 A B F J K L - 知乎 (zhihu.com))写得好。

签到

K. PTT

​ 牛魔的签到题弄一堆洋文,看不懂一点。平常帮我读题那个队友还去了namomo camp,这题读得很痛苦

void solve(){
    ll n;double c;cin>>n>>c;
    double ans=0;
    if(n>=10000000)ans=2.0;
    else if(n>9800000)ans=1.0+1.0*(n-9800000)/200000;
    else ans=1.0*(n-9500000)/300000;
    cout<<max(ans+c,0.0)<<endl;
}
signed main(){
    std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
    srand((unsigned)time(NULL));
    cout << fixed << setprecision(10);
    int t;std::cin>>t;while(t--)
    solve();
}

B. Potion(easy version)

​ 假设当前的浓度为 :\(x:y\) ,那么我们发现上一次的浓度比会是:\((ax-by):(a+b)*y\),(或者\(x\)\(y\)互换位置)。这个结果是通过先得出下一次的浓度比为:\((ax+bx+by):ay\),(或者x和y互换位置)。然后再反推得到的。当 \(a=b\) 的时候,显然\(x\)\(y\)在每一次结果中不会交换位置,即大的那个当\(x\),然后我们带入式子,即可。复杂度为\(O(logn)\)

void solve(){
    ll a,b,x,y;cin>>a>>b>>x>>y;
    ll ans=1;
    while(1){
        if(a<b)swap(a,b);
        if(a%2!=b%2){
            cout<<-1<<"\n";
            return ;
        }
        ans++;
        a-=b;a/=2ll;
        if(a==0){
            cout<<ans<<"\n";return ;
        }
    }
}

L.Chtholly and the Broken Chronograph

​ 线段树模板。创建一个cnt来维护即可。

#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define lowbit(x) (x&(-x))
#define lson l,mid,k<<1
#define rson mid+1,r,k<<1|1
#define lt k<<1
#define rt k<<1|1
using namespace std;
typedef pair<int,int> pii;
const int N=1e5+10;
typedef pair<int,int> pii;
struct segg{
    ll s,lazy;
    int cnt=0;
}seg[N<<2];
void build1(int l,int r,int k){
    if(l==r){cin>>seg[k].s;return ;}
    int mid=l+r>>1;
    build1(lson),build1(rson);
    seg[k].s=seg[lt].s+seg[rt].s;
}
void build2(int l,int r,int k){
    if(l==r){cin>>seg[k].cnt;seg[k].cnt^=1;return ;}
    int mid=l+r>>1;
    build2(lson),build2(rson);
    seg[k].cnt=seg[lt].cnt+seg[rt].cnt;
}
void pushdown(int l,int r,int k){
    if(l==r)return ;
    seg[lt].lazy+=seg[k].lazy;
    seg[rt].lazy+=seg[k].lazy;
    int mid=l+r>>1;
    seg[lt].s+=seg[k].lazy*(mid-l+1-seg[lt].cnt);
    seg[rt].s+=seg[k].lazy*(r-mid-seg[rt].cnt);
    seg[k].lazy=0;
}
void update(int l,int r,int k,int x,int y,ll z){
    pushdown(l,r,k);
    if(x<=l&&r<=y){
        seg[k].s+=(ll)(r-l+1ll-seg[k].cnt)*z;
        seg[k].lazy+=z;
        return ;
    }
    int mid=l+r>>1;
    if(x<=mid)update(lson,x,y,z);
    if(y>mid)update(rson,x,y,z);
    seg[k].s=seg[lt].s+seg[rt].s;
}
void update1(int l,int r,int k,int idx){
    pushdown(l,r,k);
    seg[k].cnt++;
    if(l==r)return ;
    int mid=l+r>>1;
    if(idx<=mid)update1(lson,idx);
    if(idx>mid)update1(rson,idx);
}
void update2(int l,int r,int k,int idx){
    pushdown(l,r,k);
    seg[k].cnt--;
    if(l==r)return ;
    int mid=l+r>>1;
    if(idx<=mid)update2(lson,idx);
    if(idx>mid)update2(rson,idx);
}
ll query(int l,int r,int k,int x,int y){
    pushdown(l,r,k);
    if(x<=l&&r<=y)return seg[k].s;
    ll res=0;
    int mid=l+r>>1;
    if(x<=mid)res+=query(lson,x,y);
    if(y>mid)res+=query(rson,x,y);
    return res;
}
void solve(){
    int n,m;cin>>n>>m;
    build1(1,n,1);
    build2(1,n,1);
    while(m--){
        int op;cin>>op;
        if(op==1){
            int idx;cin>>idx;
            update1(1,n,1,idx);
        }
        else if(op==2){
            int idx;cin>>idx;
            update2(1,n,1,idx);
        }
        else if(op==3){
            int x,y,z;cin>>x>>y>>z;
            update(1,n,1,x,y,z);
        }
        else{
            int x,y;cin>>x>>y;
            cout<<query(1,n,1,x,y)<<"\n";
        }
    }
}
signed main(){
    std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
    srand((unsigned)time(NULL));
    //int t;std::cin>>t;while(t--)
    solve();
}

J. Palindrome Reversion

​ 猜了个结论,和队友聊了会也没证明出来。具体是先两边往中间枚举,第一个不一样的坐标作为reverse子区间的一个端点,然后枚举另一个端点,哈希判断reverse后整体是否为回文。后来看了大佬[每日一棵splay](2022 Hubei Provincial Collegiate Programming Contest 题解 A B F J K L - 知乎 (zhihu.com))的博客,想到其实这一步就是把那些已经是回文的字符删去了。这么说看起来很弱智,但是我认为还是能从中汲取到一些养分的。另外上面提到的这位大佬最近好像退役了,正在考研,那我在这里也祝他能考上。

​ 赛后写这题的时候n和m写反了,以为是哈希写错了,de了好久。

typedef unsigned long long ull;
typedef pair<ull,ull> puu;
const ull mod1=19260817;
const ull mod2=19660813;
const ull base=233;

const int N=1e6+10;
typedef pair<int,int> pii;

ull pw1[N],pw2[N];
void initpw(){
    pw1[0]=pw2[0]=1;
    for(int i=1;i<=1e6;i++){
        pw1[i]=pw1[i-1]*base%mod1;
        pw2[i]=pw2[i-1]*base%mod2;
    }
}
puu gethash(char c){
    return {c*base%mod1,c*base%mod2};
}
void solve(){
    string ss;cin>>ss;
    int n=ss.size();
    string s="~";s+=ss;
    int flag=-1;
    for(int i=1;i<=n;i++){
        if(s[i]!=s[n-i+1]){flag=i;break;}
    }
    if(flag==-1){
        cout<<1<<" "<<1<<"\n";return ;
    }
    int m=n;
    string a="~";
    for(int i=flag;i<=n-flag+1;i++)a+=s[i];
    n=a.size()-1;
    puu hash1={0,0},hash2={0,0};
    for(int i=1;i<=n;i++){
        puu temp=gethash(a[i]);
        hash1.fi=(hash1.fi+temp.fi*pw1[i-1]%mod1)%mod1;
        hash1.se=(hash1.se+temp.se*pw2[i-1]%mod2)%mod2;
        hash2.fi=(hash2.fi+temp.fi*pw1[n-i]%mod1)%mod1;
        hash2.se=(hash2.se+temp.se*pw2[n-i]%mod2)%mod2;
    }
    puu hash3={0,0},hash4={0,0};
    for(int i=1;i<n;i++){
        puu temp=gethash(a[i]);
        hash3.fi=(hash3.fi+temp.fi*pw1[i-1]%mod1)%mod1;
        hash3.se=(hash3.se+temp.se*pw2[i-1]%mod2)%mod2;
        hash4.fi=(hash4.fi*base%mod1+temp.fi)%mod1;
        hash4.se=(hash4.se*base%mod2+temp.se)%mod2;
        puu k1={(hash1.fi+hash4.fi+mod1-hash3.fi)%mod1,(hash1.se+hash4.se+mod2-hash3.se)%mod2};
        puu k2={(hash2.fi+((hash3.fi+mod1-hash4.fi)%mod1)*pw1[n-i])%mod1,(hash2.se+((hash3.se+mod2-hash4.se)%mod2)*pw2[n-i])%mod2};
        if(k1==k2){
            cout<<flag<<" "<<flag+i-1<<"\n";return ;
        }
    }
    string b="~";
    for(int i=m-flag+1;i>=flag;i--)b+=s[i];
    n=b.size()-1;
    hash1={0,0},hash2={0,0};
    for(int i=1;i<=n;i++){
        puu temp=gethash(b[i]);
        hash1.fi=(hash1.fi+temp.fi*pw1[i-1]%mod1)%mod1;
        hash1.se=(hash1.se+temp.se*pw2[i-1]%mod2)%mod2;
        hash2.fi=(hash2.fi+temp.fi*pw1[n-i]%mod1)%mod1;
        hash2.se=(hash2.se+temp.se*pw2[n-i]%mod2)%mod2;
    }
    hash3={0,0},hash4={0,0};
    for(int i=1;i<n;i++){
        puu temp=gethash(b[i]);
        hash3.fi=(hash3.fi+temp.fi*pw1[i-1]%mod1)%mod1;
        hash3.se=(hash3.se+temp.se*pw2[i-1]%mod2)%mod2;
        hash4.fi=(hash4.fi*base%mod1+temp.fi)%mod1;
        hash4.se=(hash4.se*base%mod2+temp.se)%mod2;
        puu k1={(hash1.fi+hash4.fi+mod1-hash3.fi)%mod1,(hash1.se+hash4.se+mod2-hash3.se)%mod2};
        puu k2={(hash2.fi+((hash3.fi+mod1-hash4.fi)%mod1)*pw1[n-i])%mod1,(hash2.se+((hash3.se+mod2-hash4.se)%mod2)*pw2[n-i])%mod2};
        if(k1==k2){
            cout<<m-flag-i+2<<" "<<m-flag+1<<"\n";return ;
        }
    }
    cout<<-1<<" "<<-1<<"\n";
}

难度上来了,很可惜有战犯没出数学题。

C. Potion(hard version)

题面:

​ 给出a,b,x,y。问能否用刻度为a/(a+b)的量筒通过盛水倒水,使得量筒中药水的浓度为x/(x+y)。

数据范围:

​ a,b,x,y均为[1,1e18]。

解法:

​ 首先,我们将ab,xy,处理成互质形式。为表述方便,我们设c=a+b。设有n+1次倒水,第一次占比例为\((a/c)^n\),剩下的第i次比例为\((b*a^{n+1-i})/c^{n+2-i}\),然后我们将他们求和,表示成\((a^n+b*\sum_{i=1}^{n}a^{n-i}*c^{i-1})/c^{n}\),然后这个等于\(x/(x+y)\)

​ 做完了以上操作,下面我们面对的问题就是,能否从\(a^{n-i}*c^{i-1}\)中选出来某些子集,使得他们的和等于现在的m。i的范围是从1到n。于是我们只需要看当前的m是否能被a整除。如果能,那么i=1是不能选的,因为a与c互质,否则就必选。于是我们把m进行相应的改变,即如果不能整除就减去\(a^{n-1}\),如果能整除就不减,然后再看能否被c整除,能则除以c。昨晚以上操作,我们发现这个问题转化成了一个子问题,此时n等于n-1。然后我们只需要一遍遍去递归求解即可。

​ 但这样说还是感觉有十分甚至九分的抽象,因为n其实是我们假设出来的。下面给出喂饭级别的教程。

​ 0,当x和y有一方为0的时候,结束。

​ 1,观察是否有\((x+y)\mid{c}\) 。若\((x+y)\nmid{c}\),那么无解。这一条的依据是\(x+y=(a^n+b*\sum_{i=1}^{n}a^{n-i}*c^{i-1})/c^{n}\)。即倒完水后,稀释的分母为c。

​ 2,然后观察x是否能被a整除,若能,则x除以a,y则等于总的减去当前的x。若不能,y除以a,x等于总的减去当前的x。

代码:
void solve(){
    ll a,b,x,y;cin>>x>>y>>a>>b;
    int cnt=1;
    ll g=__gcd(a,b);a/=g,b/=g;
    g=__gcd(x,y);x/=g,y/=g;
    ll c=a+b;
    while(x&&y){
        if(x>y)swap(x,y);
        cnt++;
        ll z=x+y;
        if(z%c){cout<<-1<<"\n";return ;}
        z/=c;
        if(x%a==0){
            x/=a;y=z-x;
        }
        else if(y%a==0){
            y/=a;x=z-y;
        }
        else{cout<<-1<<"\n";return ;}
    }
    cout<<cnt<<"\n";
}

C. Potion(hard version)

题面:

​ 给两个数组a,b和一个正整数x,从1到n如果bi等于0,那么x=x&a[i],否则x=x|a[i]。给出q次询问,每次询问给出一个x一个k,k表示最多可以反转k个bi,要求给出每次询问的最大值。

数据范围:

​ n,q均为[1,2e5],ai为[1,\(2^{30}\))。

解法:

​ 本题的关键在于,要看出来反转最后k个bi是最优的,下面给出证明:

​ 假设i<j,然后我们对于ai,aj的每一个二进制为分开考虑,那么

​ 1,ai=aj:此时无论反转哪个,影响都一样。

​ 2,ai=1,aj=0:如果反转i而不反转j,是没有意义的,所以必须要先保证j是反转的。

​ 3,ai=0,aj=1:反转j后,无论i反转不反转,均没有影响。

​ 证明完毕后,那么我们只需要用两个前缀和与一个后缀和,来维护k次反转的结果,可以做到\(O(1)\)查询。

代码:
void solve(){
    int n,m;cin>>n>>m;
    vector<int>a(n+1);
    vector<int>b(n+1);
    vector<int>pre(n+2);vector<int>c(n+2);vector<int>suf(n+2);
    c[0]=(1ll<<30)-1ll;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++){
        cin>>b[i];
        if(b[i]){pre[i]=pre[i-1]|a[i];c[i]=c[i-1];}
        else {pre[i]=pre[i-1]&a[i];c[i]=c[i-1]&a[i];}
    }
    vector<int>pos(n+2);
    int cnt=0;
    pos[0]=n+1;
    for(int i=n;i>=1;i--){
        suf[i]=suf[i+1]|a[i];
        if(!b[i])cnt++;
        pos[cnt]=i;
    }
    while(m--){
        int x,k;cin>>x>>k;        k=min(k,cnt);
        int idx=pos[k];
        cout<<(x&c[idx-1]|pre[idx-1]|suf[idx])<<"\n";
    }
}

先写到这吧,后面的再补,主要是tmd写了别的几篇的,忘保存了,烦死了,等心情好了再写。

posted @ 2024-02-20 16:12  magic_here_tonight  阅读(172)  评论(0)    收藏  举报