Codeforces Round #665 (Div. 2) solution

重返橙名…

1401A - Distance and Axis

假如 k ≤ n k\le n kn ,那么 k k k应该为偶数.
否则, k − n k-n kn即为答案.

1401B - Ternary Sequence

简单贪心一下.

int n,a,b,c,x,y,z;

int main() {
    int T; qr(T); while(T--){
        qr(a); qr(b); qr(c);
        qr(x); qr(y); qr(z);
        int ans=min(c,y);
        c -= ans; y -= ans; ans=ans*2;
        ans -= min(b,max(z-a-c,0))*2;
        pr2(ans);
    }
}

1401C - Mere Array

设最小值为 m m m,有 若原序列和排序后的序列的某一位置均为 m m m的倍数则合法(因为可以通过 m m m实现任意 m m m倍数的交换) 否则 两个位置上的数必须相等.

int n,a[N],b[N],m;
bool v[N];
 
int main() {
    int _; qr(_); while(_--) {
        qr(n); m=inf;
        for(int i=1;i<=n;i++) qr(a[i]),b[i]=a[i],m=min(m,a[i]);
        sort(b+1,b+n+1); bool flag=1;
        for(int i=1;i<=n;i++) {
            bool u=a[i]%m==0,v=b[i]%m==0;
            if(u) {
                flag&=v;
            }
            else flag&=(a[i]==b[i]);
        }
        if(flag) puts("YES");
        else puts("NO");
    }
    return 0;
}

1401D - Maximum Distributed Tree

首先,每条边的贡献次数可以 d f s dfs dfs求出.
然后,假如 m ≥ n m\ge n mn,则可以贪心的给次数最大的边的权值设为最大.
最后,次数 和 权值 均从大到小选择即可.

int n,m,tot,sz[N],fa[N]; ll f[N],p[N];
vector<int> e[N];
void ins(int x,int y) {e[x].pb(y);}
void add(int x,int y) {ins(x,y); ins(y,x);}
 
void dfs(int x) {
    sz[x]=1;
    for(int y:e[x]) if(y^fa[x]) {
        fa[y]=x; dfs(y); sz[x]+=sz[y];
        f[++tot]=(ll)sz[y]*(n-sz[y]);
    }
}
 
int main() {
    int _; qr(_); while(_--) {
        qr(n); 
        for(int i=1;i<=n;i++) e[i].clear();
        for(int i=1,x,y;i<n;i++) qr(x),qr(y),add(x,y);
        fa[1]=0; tot=0; dfs(1);
        qr(m); for(int i=1;i<=m;i++) qr(p[i]);
        sort(p+1,p+m+1); 
        for(int i=m-1;i>=n-1;i--) p[i]=(ll)p[i]*p[i+1]%mod;
        m=min(m,n-1);
        ll ans=0; sort(f+1,f+tot+1);
        while(tot) {
            f[tot]%=mod;
            if(m) ans += (ll)f[tot]*p[m--]%mod;
            else ans += f[tot];
            tot--;
        }
        pr2(ans%mod);
    }
    return 0;
}

1401E - Divide Square

通过画图模拟可以发现:(开始时连通块数 a n s = 1 ans=1 ans=1)

  • 如果线段和两边都相交, a n s + + ans++ ans++.
  • 如果两个线段有交点, a n s + + ans++ ans++.

然后就变成了一个二维偏序问题,类似扫描线的思想做即可.

比赛时候打的丑陋的代码:

int n,m;
struct H {
	int y,l,r;
	bool operator<(H b) const {return y<b.y;}
}a[N];
 
struct V {
	int x,l,r;
};
bool cmp1(V a,V b){return a.r<b.r;}
bool cmp2(V a,V b){return a.l>b.l;}
vector<V> b[2];
 
const int T=(int)1e6;
struct Bit {
	int c[N];
	void clear() {memset(c,0,sizeof c);}
	void add(int x) { for( ;x<=T+4;x+=x&-x) c[x]++; }
	int ask(int x) {int y=0; for(  ;x;x&=x-1) y+=c[x]; return y;}
} A,B;
 
int main() {
	qr(n); qr(m);
	ll ans=1;
	for(int i=1;i<=n;i++) {
		qr(a[i].y),qr(a[i].l),qr(a[i].r);
		ans += !a[i].l&&a[i].r==T;
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=m;i++) {
		int x,l,r; qr(x); qr(l); qr(r);
		V t=(V){x,l,r};
		b[l>0].pb(t);
		if(!l&&r==T) ans++;
	}
	sort(all(b[0]),cmp1);
	sort(all(b[1]),cmp2);
	int i=0;
	for(auto it:b[0]) {
		int x=it.x,l=it.l,r=it.r;
		while(i<n&&a[i+1].y<=r) {
			++i;
			if(!a[i].l) A.add(T-a[i].r+1);
			else B.add(a[i].l+1);
		}
		ans += A.ask(T-x+1)+B.ask(x+1);
	}
	A.clear(); B.clear(); 
	i=n+1;
	for(auto it:b[1]) {
		int x=it.x,l=it.l,r=it.r;
		while(i>1&&a[i-1].y>=l) {
			i--;
			if(!a[i].l) A.add(T-a[i].r+1);
			else B.add(a[i].l+1);
		}
		ans += A.ask(T-x+1)+B.ask(x+1);
	}
	pr2(ans);
    return 0;
}

值域那么小,直接用桶排不就行了?

int n,m,c[N+5];
void add(int x,int y) {for( ;x<=N;x+=x&-x) c[x]+=y;}
int ask(int x) {int y=0; for( ;x;x&=x-1) y+=c[x]; return y;}
int ask(int l,int r) {return ask(r)-(l?ask(l-1):0);}

vector<pii> a[N+5],b[N+5];
ll ans=1;

int main() {
    qr(n); qr(m);
    for(int i=1,x,l,r;i<=n;i++){
        qr(x); qr(l); qr(r);
        a[l].pb(mk(x,1));
        a[r+1].pb(mk(x,-1));
        if(!l&&r==N) ans++;
    }
    for(int i=1,x,l,r;i<=m;i++) {
        qr(x); qr(l); qr(r);
        b[x].pb(mk(l,r));
        if(!l&&r==N) ans++;
    }
    for(int i=0,t;i<=N;i++) {
        for(auto it:a[i]) add(it.fi,it.se);
        for(auto it:b[i]) ans += ask(it.fi,it.se);
    }
    pr2(ans); return 0;
}

1401F - Reverse and Swap

我们把序列下标由0开始计,容易发现:

  • r e v e r s e ( k ) reverse(k) reverse(k)表示下标 ^ ( 2 k − 1 ) (2^k-1) (2k1)
  • s w a p ( k ) swap(k) swap(k)表示下标 ^ 2 k 2^k 2k
    所以我们可以用 v a l val val表示总的异或值.

那么我们可以用线段树维护区间和,如果询问一个线段树上的区间 [ l , r ] , 2 k = r − l + 1 [l,r],2^k=r-l+1 [l,r],2k=rl+1,可以发现 v a l val val的最小的 k k k位并不影响 [ l , r ] [l,r] [l,r]对应的原序列区间,所以我们可以通过异或前面的位找到对应的区间.

至于修改操作,就先 ^ v a l val val,找到对应位置对线段树进行修改即可.

代码来源
这里运用了zkw线段树,所以非常简洁.

#include
using namespace std;
typedef long long ll;

int n,q,val;
ll seg[1<<19|5];

void qr(int &x) {scanf("%d",&x);}

int main() {
    scanf("%d%d",&n,&q); n=1<<n;
    for(int i=n;i<2*n;i++) scanf("%lld",&seg[i]);
    for(int i=n-1; i;i--) seg[i]=seg[i*2]+seg[i*2+1];
    while(q--) {
        int op,l,r; qr(op); qr(l);
        if(op==1) {
            l--; l^=val; qr(r);
            int delta=r-seg[n+l];
            for(int i=n+l; i;i=i/2) seg[i]+=delta;
        }
        else if(op==2) val^=(1<<l)-1;
        else if(op==3) val^=1<<l;
        else {
            qr(r); l--; ll ans=0;//左闭右开.
            for(int a=n+l,b=n+r,c=val;a<b;a/=2,b/=2,c/=2) {
                if(a&1) ans+=seg[(a++)^c];
                if(b&1) ans+=seg[(--b)^c];
            }
            printf("%lld\n",ans);
        }
    }
    return 0;
}

你可能感兴趣的:(比赛)