暑假集训日记——8.18(codeforce)

E - 染色
题意:
给定一棵有 n n n个节点的无根树和 m m m个操作,操作有 2 2 2类:
1、将节点 a a a到节点 b b b路径上所有点都染成颜色 c c c
2、询问节点 a a a到节点 b b b路径上的颜色段数量(连续相同颜色被认为是同一段),如 “ 112221 ” “112221” 112221由3段组成: “ 11 ” 、 “ 222 ” 和 “ 1 ” “11”、“222”和“1” 112221
请你写一个程序依次完成这 m m m个操作。

题解:数链剖分
模板数链剖分,然后考虑线段树部分,因为着色,然后查询段数和,所以一定会使用 l z lz lz标记,然后区间合并的时候再判断左右端点是否是相同的颜色,是就将结果减一。还有一些小细节就详见代码吧
注意:这里的区间合并包括,建树的时候,查询的时候,修改的时候,还有因为是数链剖分,还有在重链上合并的时候都要计算区间端点是否相同。

ps:debug 2小时,随便加了个读入优化过了,emmmm,最近一段时间我都不打算做数链的题了…太伤了

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n, m, r, p;

int read(){
    int X=0,w=0;char ch=0;
    while(ch<'0'||ch>'9'){w|=ch=='-';ch=getchar();}
    while(ch>='0'&&ch<='9')X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
template<typename T>

struct Heavy_Light_Decomposition{
	//head u的子节点编号,to 子节点, nx 下一子节点的编号
	int head[N], to[N], nx[N], tot, cnt;
	//dep 深度,fa 父亲 ,sz 子树大小 ,son 儿子 ,w权值
	int dep[N], fa[N], sz[N], son[N], w[N];
	//top 重儿子的祖先 ,id 刨分后的编号 ,wt 刨分后的权值
	int top[N], id[N], wt[N];
	//val 维护区间和 ,lz lazy标记
	int val[N<<2], lz[N<<2],lzl[N<<2],lzr[N<<2];
	void init(int n){
		tot = cnt = 0;
		for(int i = 0; i <= n; ++i) head[i] = -1;
	}
	void add(int u, int v){
		to[tot] = v;
		nx[tot] = head[u];
		head[u] = tot++;
	}
	void dfs1(int u,int f){//初始化dep son sz fa,找出重儿子
		dep[u] = dep[f] + 1; fa[u] = f; sz[u] = 1;
		son[u] = 0;
		for(int i = head[u]; ~i; i = nx[i]){
		    int v = to[i];
		    if(v == f) continue;
		    dfs1(v, u);
		    sz[u] += sz[v];
		    if(sz[v] > sz[son[u]]) son[u] = v;
		}
	}
	void dfs2(int u,int topf){//树刨 初始化id wt top
		id[u] = ++cnt;
		wt[cnt] = w[u];
		top[u] = topf;
		if(!son[u]) return;
		dfs2(son[u], topf);
		for(int i = head[u]; ~i; i = nx[i]){
		    int v = to[i];
		    if(v == fa[u] || v == son[u]) continue;
		    dfs2(v, v);
		}
	}
	void pushdown(int rt,int l,int r){
		if(lz[rt]==-1) return ;
		int mid = (l+r) >> 1;
		lz[rt<<1] =lzl[rt<<1]=lzr[rt<<1]= lz[rt];
		lz[rt<<1|1] =lzl[rt<<1|1]=lzr[rt<<1|1]= lz[rt];
		val[rt<<1] = 1;
		val[rt<<1|1] = 1;
		lz[rt] = -1;
	}
	void pushup(int rt){
		val[rt] = val[rt<<1] + val[rt<<1|1];
		if(lzr[rt<<1]==lzl[rt<<1|1]) val[rt]--;
	}
	void build(int rt, int l, int r){
	    lz[rt]=-1;
		if(l == r){
		    val[rt] =1;
            lzr[rt]=lzl[rt]=wt[l];
		    return ;
		}
		int mid = (l+r) >> 1;
		build(rt<<1, l, mid);
		build(rt<<1|1, mid+1, r);
		lzr[rt]=lzr[rt<<1|1],lzl[rt]=lzl[rt<<1];
		pushup(rt);
	}
	T query(int rt, int l, int r, int L, int R){
		if(L <= l && R >= r){
		    return val[rt];
		}
		int mid = (l+r) >> 1; T  ans = 0;
		pushdown(rt, l, r);
		int ans1=0,ans2=0;
		if(L <= mid) ans1 += query(rt<<1, l, mid, L, R);
		if(R > mid) ans2 += query(rt<<1|1, mid+1, r, L, R);
		///ans1和ans2不一定同时有值,因为可能不是查询区间
		if(ans1&&ans2)///都为查询区间时再合并
        {
            if(lzr[rt<<1]==lzl[rt<<1|1]) ans1--;
        }
		return ans1+ans2;
	}
	int check(int rt,int l,int r,int k){///查询子叶节点的颜色
        if(l==r) return lzl[rt];
        int mid=(l+r)>>1;
        pushdown(rt,l,r);
        if(l<=k&&k<=mid)return check(rt<<1,l,mid,k);
        else if(mid<k&&k<=r)return check(rt<<1|1,mid+1,r,k);
    }
	T queryRange(int l, int r){//查询l,r的最短路路径和
		T ans = 0;
		while(top[l] != top[r]){
		    if(dep[top[l]] < dep[top[r]]) swap(l, r);
		    ans += query(1, 1, n, id[top[l]], id[l]);
		    ///重链合并
            if(check(1,1,n,id[top[l]])==check(1,1,n,id[fa[top[l]]]))
                ans--;
		    l = fa[top[l]];
		}
		if(dep[l]>dep[r]) swap(l, r);
		ans += query(1, 1, n, id[l], id[r]);
		return ans;
	}
	void update(int rt, int l, int r, int L, int R, int x){
		if(L <= l &&R >= r){
		    lz[rt]=lzl[rt]=lzr[rt]=x;
		    val[rt]=1;
		    return ;
		}
		int mid = (l+r)>>1;
		pushdown(rt, l, r);
		if(L <= mid) update(rt<<1, l, mid, L, R, x);
		if(R > mid) update(rt<<1|1, mid+1, r, L, R, x);
		lzr[rt]=lzr[rt<<1|1],lzl[rt]=lzl[rt<<1];
		pushup(rt);
	}
	//修改l,r的最短路径的权值
	void updateRange(int l, int r, int x){
		while(top[l] != top[r]){
		    if(dep[top[l]] < dep[top[r]]) swap(l, r);
		    update(1, 1, n, id[top[l]], id[l], x);
		    l = fa[top[l]];
		}
		if(dep[l]>dep[r]) swap(l, r);
		update(1, 1, n, id[l], id[r], x);
	}
};

Heavy_Light_Decomposition<int>hld;//模板

int main(){
	//n个点,m次操作,r是根节点,p模
	//scanf("%d%d", &n, &m);
	n=read(); m=read();
	hld.init(n);
	memset(hld.lz,-1,sizeof(hld.lz));
    r=1;
	for(int i = 1; i <= n; ++i){
		//scanf("%d", &hld.w[i]);
		hld.w[i]=read();
	}
	for(int i = 1; i < n; ++i){
		//int u, v; scanf("%d%d", &u, &v);
		int u=read(); int v=read();
		hld.add(u, v);
		hld.add(v, u);
	}
	hld.dfs1(r, 0);
	hld.dfs2(r, r);
	hld.build(1, 1, n);

	for(int i = 1; i <= m; ++i){
		//int id, x, y, z;
		char str;
		getchar();
		scanf("%c", &str);
		if(str=='C'){
			//scanf("%d%d%d", &x, &y, &z);
			int x=read();
            int y=read();
            int z=read();
			hld.updateRange(x, y, z);
		}
		else if(str=='Q'){
			//scanf("%d%d", &x, &y);
			int x=read();
            int y=read();
			printf("%d\n", hld.queryRange(x, y));
		}
	}
	return 0;
}

C. Nauuo and Cards
题意:
你的手上有 n n n 张牌,牌堆中有 n n n 张牌,共有 n n n 0 0 0 牌,和 n n n 张数字牌(从 1 1 1 n n n)。定义一次“操作”为将手中的牌放到牌堆的底部,并把牌堆顶端的牌拿到手中,求使用最少的操作次数能够让牌堆中的牌变为 1 , 2 , . . . , n 1,2,...,n 1,2,...,n
题解:
考虑操作是使牌堆的牌变得有序,所以要么不使用 0 0 0牌,要么连续使用几张 0 0 0牌后,再打出几张非零牌。

首先,试着在不玩任何空牌的情况下完成它。
如果这是不可能的,最好的选择是连续几个空卡,然后从 1 1 1 n n n。假设在 p i − t h pi-th pith在堆中的位置( p i = 0 p_i= 0 pi=0,如果是在手里),你必须玩至少 p i − i + 1 p_i−i + 1 pii+1空卡。所以答案是 m a x ( p i − i + 1 + n ) max (p_i- i+1+n) max(pii+1+n)

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;
const int N = 200010;
int n, a[N], b[N], p[N], ans;
int main()
{
    int i, j;
    scanf("%d", &n);
    for (i = 1; i <= n; ++i)
    {
        scanf("%d", a + i);
        p[a[i]] = 0;
    }
    for (i = 1; i <= n; ++i)
    {
        scanf("%d", b + i);
        p[b[i]] = i;
    }
    if (p[1])///如果可以不用玩空卡
    {
        for (i = 2; p[i] == p[1] + i - 1; ++i);
        if (p[i - 1] == n)///判断是否形如00001234/560001234
        {
            for (j = i; j <= n && p[j] <= j - i; ++j);
            ///判断是否可以不用空卡
            if (j > n)
            {
                printf("%d", n - i + 1);
                return 0;
            }
        }
    }
    for (i = 1; i <= n; ++i) ans = max(ans, p[i] - i + 1 + n);
    printf("%d", ans);
    return 0;
}

D. Nauuo and Circle
题意:
给出你一个包含 n 个点的树,这 n 个点编号为 1~n;给出一个圆,圆上放置 n 个位置,第 i 个位置对应树中的某个节点,并且不重复;求在圆上还原这棵树后,使得边不相交的总方案数;

题解:详细题解
结果=(入度的阶乘)*(出度的阶乘)

#include <bits/stdc++.h>
#include <cstring>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const int MAXN = 2e5 + 5;
const int MOD = 998244353;

int n,ans;
int deg[MAXN];

int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    scanf("%d",&n);
    ans = n;
    for(int i = 1;i < n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        deg[x]++; //统计度数
        deg[y]++;
        ans = (ll)ans * deg[x] % MOD * deg[y] % MOD; //直接求解
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值