ABC269
D
Content
给你若干个点和相邻点的定义,问你图中有几个连通块。
Sol
连通用并查集维护,就是这里的相邻有点怪。
Code
#include<bits/stdc++.h>
using namespace std;
const int _=1005;
int n;
int a[_],b[_];
int ff[_];
int find(int x){return ff[x]==x?x:ff[x]=find(ff[x]);}
bool check(int x1,int y1,int x2,int y2){
if(x1<x2) swap(x1,x2),swap(y1,y2);
else if(x1==x2&&y1<y2) swap(y1,y2);
if(abs(x1-x2)+abs(y1-y2)<=2&&0<=x1-x2&&x1-x2<2&&0<=y1-y2&&y1-y2<2) return 1;
return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i]>>b[i],ff[i]=i;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(i==j) continue;
if(check(a[i],b[i],a[j],b[j])){
// cout<<i<<' '<<j<<endl;
int x=find(i),y=find(j);
if(x==y) continue;
ff[x]=y;
}
}
}
int ans=0;
for(int i=1;i<=n;++i) if(ff[i]==i) ++ans;
cout<<ans<<endl;
return 0;
}
E
Content
交互题,有一个 \(n\times n\) 的棋盘,上面已经摆了 \(n-1\) 个象棋的车(即每行每列都至多有一个车),你每次能询问一个矩形区域里面有多少车,问再摆一个车能放在哪个格子里。
Sol
行和列是独立的,考虑先做行,相当于是找一个唯一的权是0的行,二分一个 \(mid\),询问行列范围 \(1\sim mid,1\sim n\),找到第一个结果为 \(mid-1\) 的行即为答案。列做法同理。
Code
#include<bits/stdc++.h>
using namespace std;
int n;
int ask(int l,int r,int x,int y){
cout<<"? "<<l<<' '<<r<<' '<<x<<' '<<y<<endl;
int t; cin>>t;
return t;
}
int main(){
cin>>n;
int l=1,r=n,ansl=-1;
while(l<=r){
int mid=l+r>>1;
if(ask(1,mid,1,n)<mid) ansl=mid,r=mid-1;
else l=mid+1;
}
int ansr=-1;
l=1,r=n;
while(l<=r){
int mid=l+r>>1;
if(ask(1,n,1,mid)<mid) ansr=mid,r=mid-1;
else l=mid+1;
}
cout<<"! "<<ansl<<' '<<ansr;
return 0;
}
F
Content
有 \(n\times m\) 的网格,每个格子上的数为 \((i-1)\times m+j\),当 \(i+j\) 为奇数时,格子上的数会变为 \(0\),询问一个矩形,问这个矩形包含元素的和。
Sol
分类讨论:
- \(i\) 为奇,\(j\) 为偶
- \(i\) 为偶,\(j\) 为奇
推式子 \(\sum\limits_i\sum\limits_j m(i-1)+j = \sum\limits_i(cnt_j\cdot m(i-1)+\sum\limits_j j)=(cnt_j\cdot m\sum\limits_i i) + (cnt_i\sum\limits_j j)\)
\(cnt_i\) 表示 \(i\) 有多少项,\(j\) 同理。
然后直接等差数列求和即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353,i2=499122177;
int n,m,q;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>m>>q;
while(q--){
int a,b,c,d; cin>>a>>b>>c>>d;
int x,y,cnt1,D,DD,cnt2,res=0;
//case 1
if(a%2==0) x=a; else x=a-1;
if(b%2==0) y=b-2; else y=b-1;
if(x<=y) cnt1=(x+y)*((y-x)*i2%mod+1)%mod*i2%mod,D=(y-x)*i2%mod+1; else cnt1=D=0;
if(c%2==0) x=c+1; else x=c;
if(d%2==0) y=d-1; else y=d;
if(x<=y) cnt2=(x+y)*((y-x)*i2%mod+1)%mod*i2%mod,DD=(y-x)*i2%mod+1; else cnt2=DD=0;
res=(res+(cnt1*m%mod*DD%mod+cnt2*D%mod)%mod)%mod;
//case 2
if(a%2==0) x=a-1; else x=a;
if(b%2==0) y=b-1; else y=b-2;
if(x<=y) cnt1=(x+y)*((y-x)*i2%mod+1)%mod*i2%mod,D=(y-x)*i2%mod+1; else cnt1=D=0;
if(c%2==0) x=c; else x=c+1;
if(d%2==0) y=d; else y=d-1;
if(x<=y) cnt2=(x+y)*((y-x)*i2%mod+1)%mod*i2%mod,DD=(y-x)*i2%mod+1; else cnt2=DD=0;
res=(res+(cnt1*m%mod*DD%mod+cnt2*D%mod)%mod)%mod;
cout<<res<<endl;
}
return 0;
}
G
Content
有 \(n\) 张正反两面写有数字的卡片,初始时全是正面,正反两面数字之和 \(M\le 2e5\),你可以翻转这 \(n\) 张卡片的任意一张。对于每个 \(k \in [0,m]\),问当前朝上的数字之和等于 \(k\) 时的最少翻转数,不能表示就为 \(-1\)。
Sol
每次翻转的增量为 \(-a_i+b_i\),先考虑增量为正数情况,因为 \(\sum -a_i+b_i \le M\) 所以不同的增量个数至多 \(\sqrt M\) 个,因为等差数列显然为最有情况,有求和公式:\(\sqrt M (\sqrt M +1)/2 = M\),增量为负类似,全部乘个 \(-1\) 就是正数了。所以不同的增量个数为 \(2 \sqrt M\)。
所以我们处理出不同的增量这个增量出现的个数,相当于是选若干个物品使得体积为 \(k\),多重背包问题,优化一下就行了。这里二进制拆分法要快于单调队列法,因为可以证明当 \(\sum a_i\le n\) 时,\(\sum a_i\log a_i\) 是 \(O(n\sqrt n)\) 级别的(证明可以看官方题解)。
二进制拆分
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
const int _=4e5+5,N=2e5;
int n,m;
int a[_],b[_],f[_],S;
gp_hash_table<int,int> M;
vector<pair<int,int>> V;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i]>>b[i],++M[b[i]-a[i]],S+=a[i];
for(auto x:M){
int i;
// cout<<x.first<<' '<<x.second<<endl;
for(i=1;i<=x.second;i<<=1) V.push_back({i*x.first,i});//,cout<<i<<' ';
V.pop_back();
V.push_back({(x.second-(i>>1)+1)*x.first,x.second-(i>>1)+1});
// cout<<x.second-(i>>1)+1<<endl;
}
memset(f,0x3f,sizeof f);
f[S+N]=0;
for(auto xx:V){
int x=xx.first,y=xx.second;
if(x>0){
for(int j=m;j>=-m+x;--j){
f[j+N]=min(f[j+N],f[j-x+N]+y);
}
}
else{
for(int j=-m;j-x<=m;++j){
f[j+N]=min(f[j+N],f[j-x+N]+y);
}
}
}
for(int i=0;i<=m;++i){
if(f[i+N]==0x3f3f3f3f) cout<<-1<<endl;
else cout<<f[i+N]<<endl;
}
return 0;
}
单调队列
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
const int _=4e5+5,N=2e5;
int n,m;
int a[_],b[_],f[_],q[_][2],l,r,S;
gp_hash_table<int,int> M;
vector<pair<int,int>> V;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i]>>b[i],++M[b[i]-a[i]],S+=a[i];
memset(f,0x3f,sizeof f);
f[S+N]=0;
for(auto xx:M){
int x=xx.first,y=xx.second;
if(x>0){
for(int j=0;j<x;++j){
l=1,r=0;
q[1][0]=-114514191;
for(int k=0;k*x+j<=m+N;++k){
int b=f[k*x+j]-k;
while(l<=r&&q[r][0]>b) --r;
q[++r][0]=b,q[r][1]=k;
while(q[l][1]+y<k) ++l;
f[k*x+j]=q[l][0]+k;
}
}
}
else{
for(int j=0;j>x;--j){
l=1,r=0;
q[1][0]=-114514191;
for(int k=0;m+N+k*x+j>=0;++k){
int b=f[m+N+k*x+j]-k;
while(l<=r&&q[r][0]>b) --r;
q[++r][0]=b,q[r][1]=k;
while(q[l][1]+y<k) ++l;
f[m+N+k*x+j]=q[l][0]+k;
}
}
}
}
for(int i=0;i<=m;++i){
if(f[i+N]==0x3f3f3f3f) cout<<-1<<endl;
else cout<<f[i+N]<<endl;
}
return 0;
}
更简单的场了,F 以前都是送的,加了350真好。