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写了别的几篇的,忘保存了,烦死了,等心情好了再写。
标签:cnt,idx,int,void,CCPC,cin,seg,2022,湖北省 From: https://www.cnblogs.com/shi5/p/18023360