C题 Swiss Stage
思路:
签到题,就是只要赢两局或者输两局之后的每一局都要进行三局两胜的对局,也就是说在没有赢两局或者输两局的时候,赢数加1只需要赢一局即可,但有的话,赢一局需要赢两局。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=1e5+10;
const int mod=1e9+7;
#define fi first
#define se second
void solve(){
int a,b;cin>>a>>b;
int ans=0;
while(a<2){
if(b==2) ans+=2;
else ans+=1;
a++;
}
ans+=2;
cout<<ans;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
J题 Graft and Transplant
思路:
容易发现如果操作的点之间没有叶子节点,那么产生的树是新树,否则产生的树是旧树,所以我们只要判断度数大于1的点的个数即可,但要特判0的情况。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=1e5+10;
const int mod=1e9+7;
#define fi first
#define se second
int n;
int du[N];
void solve(){
cin>>n;
for(int i=1;i<n;i++){
int a,b;cin>>a>>b;
du[a]++,du[b]++;
}
int sum=0;
for(int i=1;i<=n;i++){
if(du[i]>1) sum++;
}
if(sum==0){
cout<<"Bob";
return;
}
if((sum&1)==0) cout<<"Alice";
else cout<<"Bob";
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
E题 Sheep Eat Wolves
思路:
用个三维数组记录一下左边羊,狼以及人的位置来一次广搜即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e6+10;
const int M=1e6+10;
const int inf=0x3f3f3f3f;
int x,y,p,q;
int ans=inf;
int vis[200][200][3];
struct node{
int ls, lw, rs, rw, pos, res;
};
void bfs(){
queue<node> qq;
qq.push({x, y, 0, 0, 0, 0});
while(!qq.empty()){
node t = qq.front();
qq.pop();
if(vis[t.ls][t.lw][t.pos]) continue;
if(t.rs==x){
ans=min(ans,t.res);
return;
}
if(t.lw>t.ls+q&&t.pos==1 && t.ls != 0){
continue;
}
if(t.rw>t.rs+q&& t.pos==0 && t.rs != 0){
continue;
}
vis[t.ls][t.lw][t.pos] = 1;
if(t.pos == 0){
for(int i=0;i<=t.ls && i <= p ;i++){
for(int j=0;j<=t.lw && i + j <= p;j++){
qq.push({t.ls - i, t.lw -j, t.rs + i, t.rw + j, 1, t.res + 1});
}
}
}else{
for(int j=0;j<=t.rw && j <= p;j++){
qq.push({t.ls, t.lw+j, t.rs, t.rw-j, 0, t.res+1});
}
}
}
}
void solve(){
cin >> x >> y >> p >> q;
bfs();
if(ans!=inf)
cout <<ans;
else
cout << -1;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int _=1;
//cin >> _;
while(_--){
solve();
}
}
K题 Maximum Rating
思路:
对于最小的操作数即是将所有负数放在前面,再将所有正数从小到大放在后面,操作数即是前缀和大于等于1的个数。
对于最大的操作数即是将所有正数放在前面,然后操作数就是正数个数。
在最大操作数的前提下,我们每次将一个最前端的正数移到最后端,很容易知道操作数要么减一要么不变,又最终可以变成最小的操作数,所以对于最小操作数到最大操作数之间是连续的。
所以我们只需要求出每次的最小操作数l和最大的操作数r,答案就是r-l+1。
对于最大操作数我们只需要每次维护出正数个数即可,用sum1来记录,若修改的是正数则sum1--,若改成的值是正数则sum1++。
对于最小操作数,则是求每次操作完后的新数组按上述排序后前缀和大于等于1的个数。由于我们需要排序又需要求个数,我们考虑用权值线段树,因为权值线段树的叶子节点是从小到大排序且每次可以log的修改每个值的个数。
因为数据范围很大,但询问和数组大小只有1e5,我们考虑离线处理,先将所有数据读入再进行离散化。
我们要找前缀和大于等于1的位置,然后用总数组长度减去当前位置,即是前缀和大于等于1的个数。
先介绍的做法:
由于前缀和具有单调性,我们可以二分区间【1,r】,找到第一个前缀和大于等于1的在线段树中的位置。当我们找到当前位置的时候我们再去查询当前位置的前缀个数和。由于叶子节点的cnt值不一定是1,所以我们还需要找到最左边的位置,为了找到最左边的位置我们先要把当前位置的前缀值s和真实值x找出来,然后二分条件s-mid*x>0就再往左边找。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=1e6+10;
const int mod=1e9+7;
#define fi first
#define se second
#define int ll
const ll INF = -0x3f3f3f3f3f3f3f3f;
int n,q;
int a[N];
vector<int> v;
vector<pii> ask;
int len,sum1,sum2,sum;
int Find(int x){
int pos= lower_bound(v.begin(),v.end(),x)-v.begin();
return pos;
}
int tree[N*4],cnt[N*4];
void insert(int p,int l,int r,int x,int op){
if(l==r&&l==x){
cnt[p]+=op;
tree[p]+=v[x]*op;
return;
}
int mid=(l+r)>>1;
if(x<=mid) insert(p<<1,l,mid,x,op);
else insert(p<<1|1,mid+1,r,x,op);
tree[p]=tree[p<<1]+tree[p<<1|1];
cnt[p]=cnt[p<<1]+cnt[p<<1|1];
}
int query(int p,int l,int r,int x,int y){
int res=0;
if(x<=l&&r<=y){
return tree[p];
}
int mid=(l+r)>>1;
if(x<=mid) res+=query(p<<1,l,mid,x,y);
if(y>mid) res+=query(p<<1|1,mid+1,r,x,y);
return res;
}
int querycnt(int p,int l,int r,int x,int y){
int res=0;
if(x<=l&&r<=y){
return cnt[p];
}
int mid=(l+r)>>1;
if(x<=mid) res+=querycnt(p<<1,l,mid,x,y);
if(y>mid) res+=querycnt(p<<1|1,mid+1,r,x,y);
return res;
}
int queryvalue(int p,int l,int r,int x){
int res=0;
if(l==r){
return tree[p]/cnt[p];
}
int mid=(l+r)>>1;
if(x<=mid) res+=queryvalue(p<<1,l,mid,x);
if(x>mid) res+=queryvalue(p<<1|1,mid+1,r,x);
return res;
}
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>0) sum1++;
else sum2++;
v.push_back(a[i]);
sum+=a[i];
}
for(int i=1;i<=q;i++){
int a,b;cin>>a>>b;
ask.push_back({a,b});
v.push_back(a);
v.push_back(b);
}
v.push_back(INF);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
len=v.size()-1;
for(int i=1;i<=n;i++){
insert(1,1,len,Find(a[i]),1);
}
for(int i=1;i<=q;i++){
int aa=ask[i-1].first,b=ask[i-1].second;
insert(1,1,len,Find(a[aa]),-1);
insert(1,1,len,Find(b),1);
if(a[aa]>0){
sum1--;
}
else{
sum2--;
}
if(b>0){
sum1++;
}
else{
sum2++;
}
sum-=a[aa];sum+=b;
a[aa]=b;
if(sum<=0){
cout<<sum1+1<<endl;
continue;
}
int l=1,r=len;
while(l<=r){
int mid=(l+r)>>1;
if(query(1,1,len,1,mid)>=1) r=mid-1;
else l=mid+1;
}
int s=query(1,1,len,1,l);
int pos=querycnt(1,1,len,1,l);
int x=queryvalue(1,1,len,l);
int nl=1,nr=n;
while(nl<=nr){
int mid=(nl+nr)>>1;
if(s-mid*x>=1) nl=mid+1;
else nr=mid-1;
}
pos-=nr;
pos=n-pos+1;
cout<<sum1-pos+1<<endl;
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
接下来我们讲nlog的做法:
我们考虑优化掉二分查找位置时的二分算法。
由于我们要找的是前缀和大于等于1的位置,所以我们查询到一个位置它的左半边的和小于等于需要的和,我们直接加上左半边的和,然后去右边找剩下的和。
找到位置后剩下的操作和上面一个方法一样,先找前缀和,然后找真实值,二分找到最左端位置。对于寻找真实值的时候,我们仍然要求出该值在线段树中的位置,我们只需要在查询的时候加个pp来记录查询到的叶子节点即可,因为如果要查找的和大于左边的时候,左半块我们是不会进去的,也就是说我们每次都是往右边查询,也就是说我们只会找到最右端的位置,也就是我们需要的第一个大于等于1前缀和的位置。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=1e6+10;
const int mod=1e9+7;
#define fi first
#define se second
#define int ll
const int INF = -0x3f3f3f3f3f3f3f3f;
int n,q;
int a[N];
vector<int> v;
vector<pii> ask;
int len,sum1,sum2,sum;
int pp;
int Find(int x){
int pos= lower_bound(v.begin(),v.end(),x)-v.begin();
return pos;
}
int tree[N*4],cnt[N*4];
void insert(int p,int l,int r,int x,int op){
if(l==r&&l==x){
cnt[p]+=op;
tree[p]+=v[x]*op;
return;
}
int mid=(l+r)>>1;
if(x<=mid) insert(p<<1,l,mid,x,op);
else insert(p<<1|1,mid+1,r,x,op);
tree[p]=tree[p<<1]+tree[p<<1|1];
cnt[p]=cnt[p<<1]+cnt[p<<1|1];
}
int query(int p,int l,int r,int x){
int res=0;
if(l==r){
//if(cnt[p])
pp=max(pp,l);
return cnt[p];
}
int mid=(l+r)>>1;
if(tree[p<<1] < x){
res+=cnt[p<<1];
res+=query(p<<1|1,mid+1,r,x-tree[p<<1]);
}
else{
res+=query(p<<1,l,mid,x);
}
return res;
}
int querysum(int p,int l,int r,int x){
int res=0;
if(tree[p]<=x||l==r){
return tree[p];
}
int mid=(l+r)>>1;
if(tree[p<<1] < x){
res+=tree[p<<1];
res+=querysum(p<<1|1,mid+1,r,x-tree[p<<1]);
}
else{
res+=querysum(p<<1,l,mid,x);
}
return res;
}
int queryvalue(int p,int l,int r,int x){
int res=0;
if(l==r){
return tree[p]/cnt[p];
}
int mid=(l+r)>>1;
if(x<=mid) res+=queryvalue(p<<1,l,mid,x);
if(x>mid) res+=queryvalue(p<<1|1,mid+1,r,x);
return res;
}
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>0) sum1++;
else sum2++;
v.push_back(a[i]);
sum+=a[i];
}
for(int i=1;i<=q;i++){
int a,b;cin>>a>>b;
ask.push_back({a,b});
v.push_back(a);
v.push_back(b);
}
v.push_back(INF);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
len=v.size()-1;
for(int i=1;i<=n;i++){
insert(1,1,len,Find(a[i]),1);
}
for(int i=1;i<=q;i++){
int aa=ask[i-1].first,b=ask[i-1].second;
insert(1,1,len,Find(a[aa]),-1);
insert(1,1,len,Find(b),1);
if(a[aa]>0){
sum1--;
}
else{
sum2--;
}
if(b>0){
sum1++;
}
else{
sum2++;
}
sum-=a[aa];sum+=b;
a[aa]=b;
if(sum<=0){
cout<<sum1+1<<endl;
continue;
}
pp=0;
int x=1;
int l=1,r=len;
l=query(1,1,len,1);
int s=querysum(1,1,len,1);
int pos=l;
int y=queryvalue(1,1,len,pp);
int nl=1,nr=n;
while(nl<=nr){
int mid=(nl+nr)>>1;
if(s-mid*y>=1) nl=mid+1;
else nr=mid-1;
}
pos-=nr;
pos=n-pos+1;
cout<<sum1-pos+1<<endl;
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}