• [2021省选板刷]


    绿色的话主要是自己想出来的,红色的是看了题解的
    真就啥都不会。。。

    \({\color{red}{「九省联考 2018」一双木棋}}\)

    前置知识:\(minmax搜索\)
    两方都采取最优策略,问我方的最终结果,轮到我方采取对答案贡献最大的,敌方采取对答案贡献最小的搜索方案
    考虑用\(vector\)记录每一列放了多少棋子
    记忆化搜索完全可以*过去

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<map>
    #define ll long long
    
    using std::vector;
    using std::map;
    
    map<vector<int>,int>f;
    vector<int>h;//状态总数
    
    inline ll read(){
    	char a = getchar();
    	ll ans = 0,o = 1;
    	while(a != '-' && (a < '0' || a > '9'))
    	a = getchar();
    	if(a == '-')
    	o = -1,a = getchar();
    	while(a <= '9' && a >= '0')
    	ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
    	return ans * o; 
    }
    
    ll n,m;
    int A[12][12],B[12][12];
    ll inf = 0x7f7f7f7f;
    
    int dfs(int turn){//对抗搜索
    	if(f.find(h) != f.end())return f[h];
    	int ans = turn ? -inf : inf;
    	for(int i = 0;i < n;++i){
    		if((i == 0 || h[i - 1] > h[i]) && h[i] < m){
    			++h[i];
    			if(turn)
    			ans = std::max(ans,dfs(0) + A[i + 1][h[i]]);
    			else
    			ans = std::min(ans,dfs(1) - B[i + 1][h[i]]);
    			--h[i]; 
    		}
    	}
    	return f[h] = ans;
    } 
    
    int main(){
    	n = read(),m = read();
    	for(int i = 1;i <= n;++i)
    	for(int j = 1;j <= m;++j)
    	A[i][j] = read();
    	for(int i = 1;i <= n;++i)
    	for(int j = 1;j <= m;++j)
    	B[i][j] = read();
    	for(int i = 0;i < n;++i)
    	h.push_back(m);
    	f[h] = 0;//无路可走自然是0
    	for(int i = 0;i < n;++i)
    	h[i] = 0;
    	std::cout<<dfs(1); 
    }
    

    虽说不开\(-o2\)只有\(80\),但我要是在这次省选里能做到这步也就不错了

    [九省联考2018]IIIDX

    \({\color{green}{subtask1}}\)无重复数据
    考虑每个点只有一个前缀,那么显然构成一颗树形结构。
    我们先把这颗树建出来,那么作为父节点要小于每个子节点的值。
    那么我们考虑贪心,把更大一点的值留给子节点。
    注意一个点:按照我的建边方法,需要从后往前建点,因为对于并列的点来说,我们递归进子树,先遍历的一颗子树的权值会更大。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #define ll long long
    #define N 500005
    
    ll n;
    double k;
    ll v[N],top;
    
    struct P{
    	int to,next;
    }e[4 * N];
    
    int cnt,head[N],ans[N];
    
    void add(int x,int y){
    	e[++cnt].to = y;
    	e[cnt].next = head[x];
    	head[x] = cnt;
    }
    
    ll root;
    
    void dfs(int u){
    	for(int i = head[u];i;i = e[i].next){
    		int v = e[i].to;
    		dfs(v);
    	}
    	if(u != root)
    	ans[u] = v[top--];
    }
    
    int main(){
    	scanf("%lld",&n);
    	scanf("%lf",&k);
    	for(int i = 1;i <= n;++i)
    	scanf("%lld",&v[i]);
    	std::sort(v + 1,v + n + 1);
    	root = n + 1;
    	top = n;
    	for(int i = n;i >= 1;--i){
    		int dad = i / k;
    		dad = dad ? dad : root;
    		add(dad,i);
    	}
    	dfs(root);
    	for(int i = 1;i <= n;++i)
    	printf("%d ",ans[i]);
    }
    

    \({\color{red}{subtask2}}\)有重复数据
    那么明显我们第一档的贪心是不满足答案的
    我们考虑从小到大排序,那么根据我们的字典序最优法则
    字典序最大的一类题目:
    正解则是考虑按位确定:考虑从 \(1\)\(n\) 依次确定该点选了哪个值,确定 i 这个位置的值时,我们选取满足以下条件的值:

    1. 使得这个东西确定之后,存在一种 \(i+1\)\(n\) 的分配方案,使得总方案合法。
    2. 尽可能大。
      我们考虑如何判断在这一位选定的数\(x\),我们要使大于等于\(x\)的数的数量大于等于\(siz_i\)
      这个时候我们记\(f_i\)为大于第\(i\)个数的数量
      考虑进行二分求解\(x\)
      这个时候在\(1~n\)全局减去\(siz_x\)(先给子树站好位置,防止其他的父亲抢儿子)
      如果遇到了\(i\)的儿子,那么在\(res[fa[i]] ~ n\)上加回\(siz_x\)(拿回位置,由于子树是连续一串,所以不会影响贡献)
      \(f_i\)在操作过程中可能不再满足单调性,但这是由于我们是按位确定,确定完父亲后并不能判断后续儿子的情况,实际上\(f_i\)是会满足单调性的
      那么我们进行一个取\(min\)操作,操作得到的数就是最好情况下\(f_i\)最多的值
      代码鸽了。

    \({\color{red}{[九省联考2018]秘密袭击coat}}\)

    考虑对每个点进行处理,对于一个点来说,他只有两种选择,选入联通块或者不。
    记录\(f_{i,j}\)为当前处理\(S\)\(s->i\)路径上联通块中排名为\(j\)的联通块方案数

    #include<bits/stdc++.h>
    #define ll long long
    #define N 2000
    #define mod 64123
    
    int n,k,w;
    
    int d[N];
    int f[N][N],ans;
    
    struct P{
    	int to,next;
    }e[N << 1];
    
    int cnt,head[N];
    
    void add(int x,int y){
    	e[++cnt].to = y;
    	e[cnt].next = head[x];
    	head[x] = cnt;
    }
    
    ll S;
    
    inline int read(){
    	char a = getchar();
    	int ans = 0,f = 1;
    	while(a != '-' && (a < '0' || a > '9'))
    	a = getchar();
    	if(a == '-')
    	f = -1,a = getchar();
    	while(a <= '9' && a >= '0')
    	ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
    	return ans * f; 
    }
    
    void dfs(int x,int fa){
    	if(d[x] > d[S] || (d[x] == d[S] && x < S))//在集合里选上这个点的贡献 
    	for(int i = 1;i < k;++i)
    	f[x][i + 1] = f[fa][i];
    	else
    	for(int i = 1;i <= k;++i)
    	f[x][i] = f[fa][i];
    	for(int i = head[x];i;i = e[i].next){
    		int v = e[i].to;
    		if(v == fa)
    		continue;
    		dfs(v,x);
    	}
    	for(int i = 1;i <= k;++i)//更新父亲,此时相当于父亲不选
    		f[fa][i] = (f[fa][i] + f[x][i]) % mod;
    }
    
    int main(){
    	n = read(),k = read(),w = read();
    	for(int i = 1;i <= n;++i)
    	d[i] = read();
    	for(int i = 1;i < n;++i){
    		int x = read(),y = read();
    		add(x,y);
    		add(y,x);
    	}
    	for(int i = 1;i <= n;++i){
    		S = i;
    		int tot = 0;
    		for(int j = 1;j <= n;++j){
    			if(d[j] > d[i] || (d[j] == d[i] && i > j))
    			tot ++ ;
    		}
    		if(tot < k - 1) continue;
    		std::memset(f,0,sizeof(f));
    		f[i][1] = 1;
    		for(int j = head[i];j;j = e[j].next)
    		dfs(e[j].to,i);
    		ans = (ans + 1ll * f[i][k] * d[i]) % mod;
    	}
    	printf("%lld\n",ans);
    }
    

    \({\color{red}{[九省联考2018]劈配}}\)

    首先第一档\(d = 1\)明显乱做。。
    考虑对每个点每一档的老师都连边,跑网络流,如果有流量那么这一档可以,否则删掉这些老师的边,下一档
    如果找到老师,那么对每个老师都和源点连边,如果还有流量,那么证明这个老师其实还有一种方案可以让他选择,所以记录每个老师最后的有方案选择的时间就行了
    代码鸽了

    \([八省联考2018]林克卡特树\)

    题意:大概意思是求树上\(k + 1\)条链的最大和

    \({\color{green}{60分}}\)

    其实不能全算自己想出来的,对于这类树上的链有经典做法,今天才算学到。
    \(f[u][j][0/1/2]\)代表\(u\)中有\(j\)条完整的链,\(0\)是该子树的最优答案,\(1\)是以\(u\)为起点的一条链(这是唯一的不是完整的链的情况),\(2\)\(u\)处于一条链的中心。
    转移看代码。

    #include<bits/stdc++.h>
    #define ll long long
    #define N 300005
    #define K 200
    
    int n,k;
    int f[N][K][3];
    
    struct P{
    	int to,next,v;
    }e[N << 1];
    
    int head[N],cnt;
    
    void add(ll x,ll y,ll v){
    	e[++cnt].to = y;
    	e[cnt].next = head[x];
    	e[cnt].v = v;
    	head[x] = cnt;
    }
    
    void dfs(int u,int fa){
    	f[u][0][0] = f[u][0][1] = f[u][1][2] = 0;
    	for(int i = head[u];i;i = e[i].next){
    		int v = e[i].to;
    		if(v == fa)
    		continue;
    		dfs(v,u);
    		for(int j = k;j;--j){
    			f[u][j][1] = std::max(f[u][j][1],f[u][j][0] + f[v][0][1] + e[i].v);
    			for(int l = j - 1;~l;--l){
    				f[u][j][0] = std::max(f[u][j][0],f[u][l][0] + f[v][j - l][0]);
    				f[u][j][1] = std::max(f[u][j][1],std::max(f[u][l][1] + f[v][j - l][0],f[u][l][0] + f[v][j - l][1] + e[i].v));
    				f[u][j][2] = std::max(f[u][j][2],std::max(f[u][l][2] + f[v][j - l][0],f[u][l][1] + f[v][j - l - 1][1] + e[i].v));
    			}
    		}
    		f[u][0][1] = std::max(f[u][0][1],f[v][0][1] + e[i].v);
    	}
    	for(int i = 1;i <= k;++i)
    	f[u][i][0] = std::max(f[u][i][0],std::max(f[u][i - 1][1],f[u][i][2]));
    }
    
    int main(){
    	scanf("%d%d",&n,&k),++k;
    	for(int i = 1;i < n;++i){
    		ll x,y,v;
    		scanf("%lld%lld%lld",&x,&y,&v);
    		add(x,y,v);
    		add(y,x,v);
    	}
    	memset(f,~0x3f,sizeof(f));dfs(1,0);
    	std::cout<<f[1][k][0]<<std::endl;
    }
    

    \({\color{red}{WQS二分}}\)

    前置芝士:wqs二分
    看到这类限制取\(m\)个的最大值或者最小值,如果没有\(m\)限制的\(dp\)复杂度很低,而且关于原\(dp\)是一个凸函数考虑\(wqs二分\)
    看完上面那个博客就知道该怎么写了

    #include<bits/stdc++.h>
    #define reg register
    typedef long long ll;
    using namespace std;
    const int MN=3e5+5;
    int to[MN<<1],nxt[MN<<1],c[MN<<1],h[MN],cnt;
    inline void ins(int s,int t,int w){
    	to[++cnt]=t;nxt[cnt]=h[s];c[cnt]=w;h[s]=cnt;
    	to[++cnt]=s;nxt[cnt]=h[t];c[cnt]=w;h[t]=cnt;
    }
    #define chkmax(a,b) ((a)<(b)?(a)=(b):0)
    int n,k;
    ll l,r,mid;
    struct data{
    	ll val;int pos;
    	data(ll x=0,int y=0):val(x),pos(y){}
    	friend bool operator<(data a,data b){
    		ret###${\color{red}{WQS二分}}$urn a.val==b.val?a.pos>b.pos:a.val<b.val;
    	}
    	friend data operator+(data a,data b){
    		return data(a.val+b.val,a.pos+b.pos);
    	}
    	friend data operator+(data a,ll b){
    		return data(a.val+b,a.pos);
    	}
    }f[MN][3],tmp;
    int fa[MN];
    void getf(int st){
    	for(reg int i=h[st];i;i=nxt[i])
    		if(to[i]!=fa[st])fa[to[i]]=st,getf(to[i]);
    }
    void dfs(int st){
    	f[st][0]=f[st][1]=f[st][2]=data();
    	chkmax(f[st][2],tmp);
    	for(reg int i=h[st];i;i=nxt[i]){
    		if(to[i]==fa[st])continue;dfs(to[i]);
    		chkmax(f[st][2],max(f[st][2]+f[to[i]][0],f[st][1]+f[to[i]][1]+c[i]+tmp));
    		chkmax(f[st][1],max(f[st][1]+f[to[i]][0],f[st][0]+f[to[i]][1]+c[i]));
    		chkmax(f[st][0],f[st][0]+f[to[i]][0]);
    	}
    	chkmax(f[st][0],max(f[st][1]+tmp,f[st][2])); 
    }
    int main(){
    	scanf("%d%d",&n,&k);k++;
    	for(reg int i=1,s,t,w;i<n;i++)
    		scanf("%d%d%d",&s,&t,&w),ins(s,t,w);
    	l=-1e12;r=1e12;getf(1);
    	while(l<r){
            mid=(double)(l+r)/2-0.5;
            tmp=data(-mid,1);dfs(1);
            if(f[1][0].pos==k){
                printf("%lld\n",f[1][0].val+mid*k);
                return 0;
            }
            if(f[1][0].pos>k)l=mid+1;
            else r=mid;
        }
    	mid=l;tmp=data(-mid,1);dfs(1);
    	printf("%lld\n",f[1][0].val+mid*k);
    	return 0;
    }
    

    \({\color{red}{[十二省联考2019]}}{\color{green}{[春节十二响]}}\)

    为啥是双色的?
    S-我想到了启发式合并,没仔细想具体过程。
    考虑两条链的合并,把两条链丢进堆里,只保留两条链的最大值进结果链,再把较长的链的最后部分塞进结果链
    考虑直接把长链当做结果链,那么考虑启发式合并。
    解决

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5 + 6;
    int n, a[N], f;
    vector<int> e[N], o;
    priority_queue<int> q[N];
    
    inline void merge(int x, int y) {
    	if (q[x].size() < q[y].size()) swap(q[x], q[y]);
    	while (q[y].size()) {
    		o.push_back(max(q[x].top(), q[y].top()));
    		q[x].pop(), q[y].pop();
    	}
    	while (o.size()) q[x].push(o.back()), o.pop_back();
    }
    
    void dfs(int x) {
    	for (unsigned int i = 0; i < e[x].size(); i++)
    		dfs(e[x][i]), merge(x, e[x][i]);
    	q[x].push(a[x]);
    }
    
    int main() {
    	cin >> n;
    	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    	for (int i = 2; i <= n; i++) scanf("%d", &f), e[f].push_back(i);
    	dfs(1);
    	long long ans = 0;
    	while (q[1].size()) ans += q[1].top(), q[1].pop();
    	cout << ans << endl;
    	return 0;
    }
    

    \({\color{red}{[[省选联考 2020 A/B 卷] 冰火战士]}}\)

    想到了一些性质
    不过没抽象出本质的东西。
    如果两个函数一个单调增,一个单调减,如果两个函数相交是理论最大值。
    但是由于两个函数是离散的,可以考虑这样一个东西。
    那么答案是如下两个\(k\)的最大值:
    \(f_1(max(k)),f_1(k) < f_2(k)\)
    \(f_1(min(k)),f_1(k) >= f_2(k)\)
    由于函数性质,所以可以进行二分。
    如果用树状数组二分的话,有两个\(log\)直接被卡到\(60\)
    考虑树状数组倍增,对于这样一个性质树状数组\([d + 2 ^ i]\)计算的正是\([d + 1,d + 2 ^ i]\)

    #include <bits/stdc++.h>
    #define rep(i, x, y) for (int i = x; i <= y; i++)
    using namespace std;
    
    const int N = 2e6 + 10;
    int Q, n;
    int h[N], tot, suf[N];
    
    struct atom {
        int id, x, y;
    } star[N];
    
    struct BIT {
        int C[N];
        int lowbit(int x) {
            return x & -x;
        }
        void add(int x, int v) {
            for (; x <= tot; x += lowbit(x))
                C[x] += v;
        }
        int ask(int x) {
            int ret = 0;
    
            for (; x; x -= lowbit(x))
                ret += C[x];
    
            return ret;
        }
    } ice, fire;
    
    int calc(int t) {
        if (t < 1 || t > tot)
            return -1;
    
        return min(ice.ask(t), fire.ask(tot) - fire.ask(t - 1));
    }
    
    int main() {
        cin >> Q;
        int op, k, x, y;
        rep(i, 1, Q) {
            scanf("%d%d", &op, &k);
            if (op == 1) {
                scanf("%d%d", &x, &y);
            } else {
                x = star[k].x, y = -star[k].y, k = star[k].id;
            }
            star[i] = (atom) {
                k, x, y
            };
            h[i] = x;
        }
        sort(h + 1, h + Q + 1);
        tot = unique(h + 1, h + Q + 1) - h - 1;
        rep(q, 1, Q) {
            int x = lower_bound(h + 1, h + tot + 1, star[q].x) - h, y = star[q].y, k = star[q].id;
            if (!k)
                ice.add(x, y);
            else
                fire.add(x, y), suf[x] += y;
            int p = 0, all = fire.ask(tot), sum = 0;
            for (int i = 21; i >= 0; i--) {
                int u = (p | (1 << i));
                if (u <= tot && sum + ice.C[u] + fire.C[u] <= all + suf[u])
                    p = u, sum += ice.C[u] + fire.C[u];
            }
            int w1 = calc(p), w2 = calc(p + 1);
            if (w1 <= 0 && w2 <= 0)
                puts("Peace");
            else if (w1 > w2)
                printf("%d %d\n", h[p], w1 * 2);
            else {
                p = 0, sum = 0;
                for (int i = 21; i >= 0; i--) {
                    int u = (p | (1 << i));
    
                    if (u <= tot && all - sum - fire.C[u] + suf[u] >= w2)
                        p = u, sum += fire.C[u];
                }
                printf("%d %d\n", h[p], w2 * 2);
            }
        }
        return 0;
    }//这个常数也巨大无比。。
    

    \({\color{red}{[省选联考 2020 A 卷] 树}}\)

    再一次感受到自己的弱小
    考虑\(01tire\),(感谢\(hx\)大佬教了我一晚上的\(01tire\)
    \(01tire\)中维护子树里的\(v + d\),考虑子树向上的父亲,每上一次相当于把\(01tire\)做了一遍\(+1\)操作
    考虑对于每个点的\(01tire\)做完就扔了,所以可以直接合并不用记录历史信息。

    #include<iostream>
    #include<cstdio>
    #define ll long long
    #define N 600005
    #define MOD  
    ll n,cnt;
    int head[N];
    struct P{
    	int to,next;
    }e[N];
    
    inline void add(int x,int y){
    	e[++cnt].to = y;
    	e[cnt].next = head[x];
    	head[x] = cnt;
    }
    
    struct Q{
    	int cnt;
    	int res;
    	int dep;
    	int son[2];
    }T[N * 22]; 
    
    int rt[N];
    
    int tot;
    
    inline void up(int p){
    	#define ls T[p].son[0]
    	#define rs T[p].son[1]
    	T[p].res = T[ls].res ^ T[rs].res ^ ((T[rs].cnt & 1) << T[p].dep);
    }
    
    inline void ins(int &p,int i,int d){
    	if(!p){
    		p = ++tot;
    		T[p].dep = d;
    	}
    	++T[p].cnt;
    	if(d > 20)return;
    	ins(T[p].son[(i >> d) & 1],i,d + 1);
    	up(p);
    }
    
    inline int merge(int x,int y){
    	if(!x || !y)
    	return x ? x : y;
    	T[x].cnt += T[y].cnt;
    	T[x].son[0] = merge(T[x].son[0],T[y].son[0]);
    	T[x].son[1] = merge(T[x].son[1],T[y].son[1]);
    	up(x);
    	return x;
    }
    
    
    inline void add(int p){
    	if(!p || T[p].dep > 20)return;
    	add(T[p].son[1]);
    	std::swap(T[p].son[1],T[p].son[0]);
    	up(p);
    }
    
    int v[N],ans[N];
    
    inline void del(int now){
    	for(int i = head[now];i;i = e[i].next){
    		del(e[i].to);
    		rt[now] = merge(rt[now],rt[e[i].to]);
    	}
    	add(rt[now]);
    	ins(rt[now],v[now],0);
    	ans[now] = T[rt[now]].res;
    }
    
    int main(){
    	scanf("%lld",&n);
    	for(int i = 1;i <= n;++i){
    		scanf("%d",&v[i]);
    	}
    	for(int i = 2;i <= n;++i){
    		int tp;
    		scanf("%d",&tp);
    		add(tp,i);
    	}
    	del(1);
    	ll rans = 0;
    	for(int i = 1;i <= n;++i){
    		rans += ans[i];
    	}
    	std::cout<<rans<<std::endl;
    }
    

    \({\color{red}{[省选联考 2020 B 卷] 丁香之路}}\)

    对于经典的模型的认知还是不够。
    看到这种起点终点度数是\(1\),路径度数是\(0 (mod\ 2)\)的,就应该想到欧拉回路。(这个没多大懂的样子)
    考虑\(s--t\)连上,那么就是一个欧拉回路。
    考虑先进行一个满足度数的限制,考虑对于一个奇点,那么就往他身后那个点连边。
    那么我们现在确实满足了度数的限制,但并没有保证联通。
    我们考虑缩点并使用最小生成树,用最小生成树的两倍来加入答案保证联通
    代码鸽了

    \({\color{green}{ [省选联考 2020 B 卷] 消息传递}}\)

    考虑树剖,换根。
    随便做了。

    \({\color{red}{ [省选联考 2020 B 卷] 消息传递}}\)

    有淀粉质的做法,不过我的淀粉质学的一言难尽
    所以淀粉质的做法鸽了。

    \({\color{red}{[十二省联考2019]异或粽子}}\)

    自己想出了一个很难实现的东西,来看看答案是怎么做的。
    考虑前缀异或,于是答案变成了\(k\)对数的异或值和最大,考虑将\((i,j)(j,i)\)视为不同的对,那么可以考虑进行\(2k\)对操作,其实也可以不用,不过可能需要可持久化的东西。
    那么丢到堆里处理,具体的。
    先将所有的\(a_i\)将与他最大的异或值丢入堆,然后把堆顶的第二位的异或值丢入,以此类推。
    由于只会有\(2k\)次加入取出操作,复杂度是对的。
    这个方法很巧妙

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #define ll long long
    #define N 20000000+10
    
    ll n,k;
    ll num[N],pre[N],to[N];
    
    inline ll read(){
    	ll ans = 0;
    	char a = getchar();
    	while(a < '0' || a > '9')a = getchar();
    	while(a <= '9' && a >= '0')ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
    	return ans;
    } 
    
    struct Trie{
    	int ch[2];
    	int cnt;
    	Trie(){cnt = 0;}
    }T[N];
    
    int cnt = 0;
    
    inline void ins(ll a){
    	ll u = 0;
    	for(int i = 31;i >= 0;--i){
    		T[u].cnt ++ ;
    		if(T[u].ch[(a >> i) & 1])
    		u = T[u].ch[(a >> i) & 1];
    		else
    		T[u].ch[(a >> i) & 1] = ++cnt,u = T[u].ch[(a >> i) & 1];;
    	}
    	T[u].cnt ++ ;
    }
    
    struct P{ll key,id;};
    
    bool operator < (P a,P b){return a.key < b.key;}
    
    std::priority_queue<P>QWQ;
    
    inline ll get(ll a,ll rk){
    	ll u = 0,ans = 0;
    	for(int i = 31;i >= 0;--i){
    		if(!T[u].ch[(((a >> i) & 1) ^ 1)])
    		u = T[u].ch[(((a >> i) & 1))];
    		else
    		if(rk <= T[T[u].ch[(((a >> i) & 1) ^ 1)]].cnt)
    		u = T[u].ch[(((a >> i) & 1) ^ 1)],ans |= (1LL << i);
    		else
    		rk -= T[T[u].ch[(((a >> i) & 1) ^ 1)]].cnt,u = T[u].ch[(a >> i) & 1];
    	}
    	return ans;
    }
    
    ll ans;
    
    int main(){
    //freopen("q.in","r",stdin);
    //freopen("q.out","w",stdout);
    	n = read(),k = read()<<1;
    	for(int i = 1;i <= n;++i)
    	num[i] = read(),pre[i] = pre[i - 1] ^ num[i],to[i] = 1;
    	for(int i = 0;i <= n;++i)
    	ins(pre[i]);
    	//puts("");
    	for(int i = 0;i <= n;++i){
    		QWQ.push((P){get(pre[i],to[i]),i});
    		to[i] ++ ;
    	}
    	for(int i = 1;i <= k;++i){
    		P u = QWQ.top();
    		QWQ.pop();
    		ans += u.key;
    		//std::cout<<u.key<<std::endl;
    		if(to[u.id] <= n)
    		QWQ.push((P){get(pre[u.id],to[u.id]),u.id});
    		to[u.id] ++ ;
    	}
    	std::cout<<(ans >> 1);
    	return 0;
    }
    

    这代码好像有点问题,可能一些精度有点问题,只能拿35,不过不想重构了。

    \({\color{green}{ [六省联考 2017] 相逢是问候}}\)

    我会了,但没有完全会
    考虑那道上帝和与集合的正确用法
    我主要卡住的原因是我忘记了扩展欧拉公式,这个该记得的,之前打\(AGC\)的时候也给忘了
    \(a^k \equiv a^{k\mod\varphi(p) + \varphi(p)} \pmod p\)(注意当\(k < \varphi(p)\)时,不用加\(\varphi(p)\)
    考虑维护的时候暴力修改,因为递归一次\(\varphi\)会缩小一半左右
    所以每个点只有\(log\)次操作
    线段树维护一下\(\varphi\)是不是\(1\)就行了
    好像这个题有点卡常,不是很想写(

    \({\color{green}{[[中山市选]杀人游戏}}\)

    随便缩个点,然后考虑一个强连通分量里的点可以知道,统计入度为\(0\)的点来统计答案。

    \({\color{green}{[POI2011]ROT-Tree Rotations}}\)

    想到一半了,如果继续想下去明显能出来。
    考虑左子树和右子树交换后,只改变跨越左子树和右子树的逆序对,考虑维护这个就行。
    用一个类似于线段树套权值线段树的东西,权值线段树合并的东西来做。

    #include<bits/stdc++.h>
    
    #define N 200010
    #define int long long
    
    using namespace std;
    
    struct Tree
    {
    	int ch[2],size;
    }t[N<<5];
    
    int n,tot,ans,num1,num2;
    
    int update(int l,int r,int val)
    {
    	int u=++tot;
    	t[u].size=1;
    	if(l==r) return u;
    	int mid=(l+r)>>1;
    	if(val<=mid) t[u].ch[0]=update(l,mid,val);
    	else t[u].ch[1]=update(mid+1,r,val);
    	return u;
    }
    
    int merge(int a,int b,int l,int r)//把b合并至a
    {
    	if(!a||!b) return a+b;
    	if(l==r)
    	{
    		t[a].size+=t[b].size;
    		return a;
    	}
    	num1+=t[t[a].ch[1]].size*t[t[b].ch[0]].size;//不交换的答案
    	num2+=t[t[b].ch[1]].size*t[t[a].ch[0]].size;//交换后的答案
    	int mid=(l+r)>>1;
    	t[a].ch[0]=merge(t[a].ch[0],t[b].ch[0],l,mid);
    	t[a].ch[1]=merge(t[a].ch[1],t[b].ch[1],mid+1,r);
    	t[a].size+=t[b].size;
    	return a;
    }
    
    int dfs()
    {
    	int u,val;
    	scanf("%lld",&val);
    	if(!val)
    	{
    		int lc=dfs(),rc=dfs();
    		num1=num2=0;
    		u=merge(lc,rc,1,n);
    		ans+=min(num1,num2);//ans加上较小值
    	}
    	else u=update(1,n,val);
    	return u;
    }
    
    signed main()
    {
    	scanf("%lld",&n);
    	dfs();
    	printf("%lld\n",ans);
    	return 0;
    }
    

    \({\color{red}{ [SCOI2011]棘手的操作}}\)

    左偏树合并。
    不会。
    考虑并查集,重构一下序列,然后变成序列操作。

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #define p2 p << 1
    #define p3 p << 1 | 1
    using namespace std;
    inline int read() {
        int res = 0; bool bo = 0; char c;
        while (((c = getchar()) < '0' || c > '9') && c != '-');
        if (c == '-') bo = 1; else res = c - 48;
        while ((c = getchar()) >= '0' && c <= '9')
            res = (res << 3) + (res << 1) + (c - 48);
        return bo ? ~res + 1 : res;
    }
    inline char get1() {
        char c; while ((c = getchar()) != 'A' && c != 'F' && c != 'U');
        return c;
    }
    inline int get2() {
        char c; while ((c = getchar()) < '0' || c > '9');
        return c - 48;
    }
    const int N = 3e5 + 5;
    int n, a[N], fa[N], pre[N], suf[N], fir[N], lst[N], L[N], R[N],
    pos[N], idx[N], T[N << 2], add[N << 2];
    struct cyx {int op, x, y;} que[N];
    void build(int l, int r, int p) {
        if (l == r) return (void) (T[p] = a[idx[l]]);
        int mid = l + r >> 1;
        build(l, mid, p2); build(mid + 1, r, p3);
        T[p] = max(T[p2], T[p3]);
    }
    void change(int l, int r, int s, int e, int v, int p) {
        if (l == s && r == e) return (void) (add[p] += v);
        int mid = l + r >> 1; add[p2] += add[p]; add[p3] += add[p];
        add[p] = 0; if (e <= mid) change(l, mid, s, e, v, p2);
        else if (s >= mid + 1) change(mid + 1, r, s, e, v, p3);
        else change(l, mid, s, mid, v, p2),
            change(mid + 1, r, mid + 1, e, v, p3);
        T[p] = max(T[p2] + add[p2], T[p3] + add[p3]);
    }
    int ask(int l, int r, int s, int e, int p) {
        if (l == s && r == e) return T[p] + add[p];
        int mid = l + r >> 1, res; add[p2] += add[p]; add[p3] += add[p];
        add[p] = 0; if (e <= mid) res = ask(l, mid, s, e, p2);
        else if (s >= mid + 1) res = ask(mid + 1, r, s, e, p3);
        else res = max(ask(l, mid, s, mid, p2),
            ask(mid + 1, r, mid + 1, e, p3));
        T[p] = max(T[p2] + add[p2], T[p3] + add[p3]);
        return res;
    }
    int cx(int x) {
        if (fa[x] != x) fa[x] = cx(fa[x]);
        return fa[x];
    }
    void zm(int x, int y) {
        int ix = cx(x), iy = cx(y);
        if (ix != iy) {
            fa[iy] = ix;
            int u = lst[ix], v = fir[iy]; lst[ix] = lst[iy];
            pre[u] = v; suf[v] = u;
        }
    }
    void wy(int x, int y) {
        int ix = cx(x), iy = cx(y);
        if (ix != iy) {
            fa[iy] = ix;
            if (L[ix] < L[iy]) R[ix] = R[iy];
            else L[ix] = L[iy];
        }
    }
    int main() {
        int i, j, Q, id, tot = 0; n = read();
        for (i = 1; i <= n; i++) a[i] = read(),
            fa[i] = fir[i] = lst[i] = i;
        Q = read(); for (i = 1; i <= Q; i++) {
            char c = get1(); switch(c) {
                case 'U':
                    que[i].x = read(); que[i].y = read();
                    zm(que[i].x, que[i].y); que[i].op = 1; 
                    break;
                case 'A': id = get2(); que[i].x = read();
                    if (id < 3) que[i].y = read();
                    que[i].op = id + 1;
                    break;
                case 'F': id = get2();
                    if (id < 3) que[i].x = read();
                    que[i].op = id + 4;
                    break;
            }
        }
        for (i = 1; i <= n; i++) if (!pre[i])
            for (j = i; j; j = suf[j])
                idx[pos[j] = ++tot] = j;
        for (i = 1; i <= n; i++) fa[i] = i, L[i] = R[i] = pos[i];
        build(1, n, 1);
        for (i = 1; i <= Q; i++) switch(que[i].op) {
            case 1: wy(que[i].x, que[i].y); break;
            case 2: change(1, n, pos[que[i].x], pos[que[i].x],
                que[i].y, 1); break;
            case 3: id = cx(que[i].x);
                change(1, n, L[id], R[id], que[i].y, 1);
                break;
            case 4: change(1, n, 1, n, que[i].x, 1); break;
            case 5: printf("%d\n", ask(1, n, pos[que[i].x],
                pos[que[i].x], 1)); break;
            case 6: id = cx(que[i].x);
                printf("%d\n", ask(1, n, L[id], R[id], 1));
                break;
            case 7: printf("%d\n", ask(1, n, 1, n, 1));
                break;
        }
        return 0;
    }
    
  • 相关阅读:
    java与C#、.NET AES加密、解密 解决方案
    java 类名.class、object.getClass()和Class.forName()的区别 精析
    java instanceof和isInstance的关系 精析
    java 反汇编class文件
    织梦默认分页样式改动 解决分页列表显示,去掉li
    The method Inflate() in android
    leetcode第一刷_Edit Distance
    codeforces round #264(div2)
    android开发游记:SpringView 下拉刷新的高效解决方式,定制你自己风格的拖拽页面
    《linux 内核全然剖析》 chapter 4 80x86 保护模式极其编程
  • 原文地址:https://www.cnblogs.com/dixiao/p/14576792.html
Copyright © 2020-2023  润新知