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” “11”、“222”和“1”。
请你写一个程序依次完成这 m m m个操作。
题解:数链剖分
模板数链剖分,然后考虑线段树部分,因为着色,然后查询段数和,所以一定会使用 l z lz lz标记,然后区间合并的时候再判断左右端点是否是相同的颜色,是就将结果减一。还有一些小细节就详见代码吧
注意:这里的区间合并包括,建树的时候,查询的时候,修改的时候,还有因为是数链剖分,还有在重链上合并的时候都要计算区间端点是否相同。
ps:debug 2小时,随便加了个读入优化过了,emmmm,最近一段时间我都不打算做数链的题了…太伤了
#include
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 pi−th在堆中的位置( p i = 0 p_i= 0 pi=0,如果是在手里),你必须玩至少 p i − i + 1 p_i−i + 1 pi−i+1空卡。所以答案是 m a x ( p i − i + 1 + n ) max (p_i- i+1+n) max(pi−i+1+n)
#include
#include
#include
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
#include
#include
#include
#include
#include
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;
}