A. 数数

排好序从两头贪心即可


B. 数树

首先很容易想到容斥

如果选择的边集的相关点集有点的度数大于 \(1\) 是不合法的

也就是说一定形成若干条长度不一的链

要给这些链上的点安排排列中的数,方案数其实就是 \((n-k)!\)

因为一条链开头的值确定了整条链的值就确定了

发现暴力算是 \(2^n\),考虑选择边集数量一定时贡献是否可以一起算

树形背包即可,算出以 \(1\) 为根的子树内选 \(k\) 条边的方案数

由于入度出度不超过 \(1\) 的限制,\(dp\) 加两维 \(0/1\) 表示有没有出/入边

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5005;
const int maxm=10005;
const int mod=998244353;
int n,hd[maxn],cnt,f[maxn][maxn][2][2],ans,fa[maxn],x,y,frac[maxn],siz[maxn],g[maxn][2][2];
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
struct Edge{
int nxt,to,val;
}edge[maxm];
void add(int u,int v,int w){
edge[++cnt].nxt=hd[u];
edge[cnt].to=v;
edge[cnt].val=w;
hd[u]=cnt;
return ;
}
void dfs(int u){
siz[u]=1;
f[u][0][0][0]=1;
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa[u])continue;
fa[v]=u;
dfs(v);
// memset(g,0,sizeof g);
for(int j=siz[u];j>=0;j--){
for(int k=siz[v];k>=0;k--){
int sum=(f[v][k][0][0]+f[v][k][0][1]+f[v][k][1][0]+f[v][k][1][1])%mod;
for(int l=0;l<2;l++){
for(int r=0;r<2;r++){
g[j+k][l][r]=(g[j+k][l][r]+f[u][j][l][r]*sum%mod)%mod;
}
}
if(edge[i].val){
g[j+k+1][1][0]=(g[j+k+1][1][0]+f[u][j][0][0]*(f[v][k][0][0]+f[v][k][1][0])%mod)%mod;
g[j+k+1][1][1]=(g[j+k+1][1][1]+f[u][j][0][1]*(f[v][k][0][0]+f[v][k][1][0])%mod)%mod;
}
else{
g[j+k+1][0][1]=(g[j+k+1][0][1]+f[u][j][0][0]*(f[v][k][0][0]+f[v][k][0][1])%mod)%mod;
g[j+k+1][1][1]=(g[j+k+1][1][1]+f[u][j][1][0]*(f[v][k][0][0]+f[v][k][0][1])%mod)%mod;
}
}
}
siz[u]+=siz[v];
for(int j=0;j<=siz[u];j++){
for(int l=0;l<2;l++){
for(int r=0;r<2;r++){
f[u][j][l][r]=g[j][l][r];
g[j][l][r]=0;
}
}
}
}
return ;
}
signed main(){
n=read();
for(int i=1;i<=n-1;i++){
x=read();
y=read();
add(x,y,0);
add(y,x,1);
}
dfs(1);
frac[0]=1;
for(int i=1;i<=n;i++)frac[i]=frac[i-1]*i%mod;
int op=1;
for(int i=0;i<=n-1;i++){
int sum=0;
for(int j=0;j<=1;j++){
for(int k=0;k<=1;k++){
// cout<<f[1][i][j][k]<<" ";
sum=(sum+f[1][i][j][k])%mod;
}
}
// cout<<endl;
// cout<<sum<<endl;
ans=(ans+op*sum*frac[n-i]%mod)%mod;
ans=(ans+mod)%mod;
op=-op;
}
cout<<ans;
return 0;
}

C. 鼠树

发现单个白点的权值由其归属点的累加可以得出,子树和操作是子树内黑点总和以及一部分白点由其归属点得出

所以只需要维护黑点信息即可

记录每个黑点的权值,管辖点的个数,以及二者的乘积

当加入一个黑点的时候,其管辖点大小由子树内大小之和推出,权值设为归属点权值,并相应地更改归属点大小

当删除一个黑点的时候,由于要保留贡献,所以直接对这棵子树做区间加,并做一次 \(4\) 操作把子树内其他黑点多算的部分减掉

最后是动态维护归属点的问题:可以树剖并在每条重链上开 \(set\) 维护黑点深度,查询时在重链上二分即可

整个算法时间复杂度 \(nlogn\)

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int unsigned int
const int maxn=3e5+5;
const int maxm=3e5+5;
int n,m,fa[maxn],hd[maxn],cnt,son[maxn],siz[maxn],id[maxn],re[maxn],tp[maxn],tot,x,y,w,op,dep[maxn],c[maxn],c1[maxn];
bool col[maxn];
set<int>s[maxn];
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
struct Edge{
int nxt,to;
}edge[maxm];
void add_edge(int u,int v){
edge[++cnt].nxt=hd[u];
edge[cnt].to=v;
hd[u]=cnt;
return ;
}
void dfs(int u){
siz[u]=1;
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa[u])continue;
dep[v]=dep[u]+1;
fa[v]=u;
dfs(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
return ;
}
void dfs1(int u,int top){
tp[u]=top;
id[u]=++tot;
re[tot]=u;
if(son[u])dfs1(son[u],top);
for(int i=hd[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==son[u]||v==fa[u])continue;
dfs1(v,v);
}
return ;
}
void add(int x,int w){
for(;x<=n;x+=x&-x)c[x]+=w;
}
void add1(int x,int w){
for(;x<=n;x+=x&-x)c1[x]+=w;
}
int ask(int x){
int ans=0;
for(;x;x-=x&-x)ans+=c[x];
return ans;
}
int ask1(int x){
int ans=0;
for(;x;x-=x&-x)ans+=c1[x];
return ans;
}
void change(int l,int r,int w){
add(l,w);
add(r+1,-w);
add1(l,w*(1-l));
add1(r+1,w*(l-1));
add1(r+1,w*(r-l+1));
return ;
}
int query(int l,int r){
return ask(r)*r+ask1(r)-ask(l-1)*(l-1)-ask1(l-1);
}
int get_t(int x){
while(1){
if(!s[tp[x]].empty()){
if(*s[tp[x]].begin()<=dep[x]){
set<int>::iterator it=s[tp[x]].upper_bound(dep[x]);
it--;
return re[id[x]-(dep[x]-(*it))];
}
}
x=fa[tp[x]];
}
return 0;
}
struct seg{
int l,r,siz,val,sum,lazy;
}t[maxn*4];
void build(int p,int l,int r){
t[p].l=l;
t[p].r=r;
if(l==r)return ;
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
return ;
}
void update(int p){
t[p].siz=t[p<<1].siz+t[p<<1|1].siz;
t[p].val=t[p<<1].val+t[p<<1|1].val;
t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
return ;
}
void dospread(int p,int w){
t[p].val+=w;
t[p].sum+=w*t[p].siz;
t[p].lazy+=w;
return ;
}
void spread(int p){
dospread(p<<1,t[p].lazy);
dospread(p<<1|1,t[p].lazy);
t[p].lazy=0;
return ;
}
void change_val(int p,int l,int r,int w){
if(l>r)return ;
if(t[p].l>=l&&t[p].r<=r){
dospread(p,w);
return ;
}
if(t[p].lazy)spread(p);
int mid=t[p].l+t[p].r>>1;
if(l<=mid)change_val(p<<1,l,r,w);
if(r>mid)change_val(p<<1|1,l,r,w);
update(p);
return ;
}
void change_siz(int p,int pos,int w){
if(t[p].l==t[p].r&&t[p].l==pos){
t[p].siz+=w;
t[p].sum+=w*t[p].val;
return ;
}
if(t[p].lazy)spread(p);
int mid=t[p].l+t[p].r>>1;
if(pos<=mid)change_siz(p<<1,pos,w);
else change_siz(p<<1|1,pos,w);
update(p);
return ;
}
int ask_val(int p,int pos){
if(t[p].l==t[p].r&&t[p].l==pos){
return t[p].val;
}
if(t[p].lazy)spread(p);
int mid=t[p].l+t[p].r>>1;
if(pos<=mid)return ask_val(p<<1,pos);
return ask_val(p<<1|1,pos);
}
int ask_siz(int p,int l,int r){
if(l>r)return 0;
if(t[p].l>=l&&t[p].r<=r)return t[p].siz;
if(t[p].lazy)spread(p);
int mid=t[p].l+t[p].r>>1,ans=0;
if(l<=mid)ans=ask_siz(p<<1,l,r);
if(r>mid)ans+=ask_siz(p<<1|1,l,r);
return ans;
}
int ask_sum(int p,int l,int r){
if(l>r)return 0;
if(t[p].l>=l&&t[p].r<=r)return t[p].sum;
if(t[p].lazy)spread(p);
int mid=t[p].l+t[p].r>>1,ans=0;
if(l<=mid)ans=ask_sum(p<<1,l,r);
if(r>mid)ans+=ask_sum(p<<1|1,l,r);
return ans;
}
void cover(int p,int pos,int w){
if(t[p].l==t[p].r&&t[p].l==pos){
t[p].val=w;
t[p].sum=w*t[p].siz;
return ;
}
if(t[p].lazy)spread(p);
int mid=t[p].l+t[p].r>>1;
if(pos<=mid)cover(p<<1,pos,w);
else cover(p<<1|1,pos,w);
update(p);
return ;
}
void rev(int x){
if(col[x]){
col[x]=false;
int cnt=siz[x]-ask_siz(1,id[x]+1,id[x]+siz[x]-1);
int y=get_t(fa[x]);
s[tp[x]].erase(dep[x]);
int val1=ask_val(1,id[x]);
int val2=ask_val(1,id[y]);
change_siz(1,id[x],-cnt);
cover(1,id[x],0);
change_siz(1,id[y],cnt);
change(id[x],id[x]+siz[x]-1,val1-val2);
change_val(1,id[x],id[x]+siz[x]-1,val2-val1);
}
else{
col[x]=true;
int cnt=siz[x]-ask_siz(1,id[x]+1,id[x]+siz[x]-1);
int y=get_t(x);
s[tp[x]].insert(dep[x]);
int val2=ask_val(1,id[y]);
change_siz(1,id[x],cnt);
change_siz(1,id[y],-cnt);
cover(1,id[x],val2);
}
return ;
}
signed main(){
n=read();
m=read();
for(int i=2;i<=n;i++){
fa[i]=read();
add_edge(fa[i],i);
}
dep[1]=1;
dfs(1);
dfs1(1,1);
build(1,1,n);
col[1]=true;
change_siz(1,id[1],siz[1]);
s[1].insert(1);
while(m--){
op=read(),x=read();
if(op==1){
printf("%u\n",ask_val(1,id[get_t(x)])+query(id[x],id[x]));
}
if(op==2){
w=read();
change_val(1,id[x],id[x],w);
}
if(op==3){
int ans=ask_val(1,id[get_t(x)])*(siz[x]-ask_siz(1,id[x]+1,id[x]+siz[x]-1));
ans+=query(id[x],id[x]+siz[x]-1);
ans+=ask_sum(1,id[x]+1,id[x]+siz[x]-1);
printf("%u\n",ans);
}
if(op==4){
w=read();
change_val(1,id[x],id[x]+siz[x]-1,w);
}
if(op==5||op==6){
rev(x);
}
}
return 0;
}

noip模拟46的更多相关文章

  1. Noip模拟46 2021.8.23

    给了签到题,但除了签到题其他的什么也不会.... T1 数数 人均$AC$,没什么好说的,就是排个序,然后双指针交换着往中间移 1 #include<bits/stdc++.h> 2 #d ...

  2. [NOIP模拟46]鼠树

    神仙题. 首先不考虑把黑点变白,发现每个白点的信息与它的归属点是相同的.可以在线段树中只维护黑点的信息,再记录$DFS$序上每个点之前黑点个数的前缀和,每次操作可以二分出该点的归属点进行操作. 具体维 ...

  3. 8.23考试总结(NOIP模拟46)[数数·数树·鼠树·ckw的树]

    T1 数数 解题思路 大概是一个签到题的感觉...(但是 pyt 并没有签上) 第一题当然可以找规律,但是咱们还是老老实实搞正解吧... 先从小到大拍个序,这样可以保证 \(a_l<a_r\) ...

  4. [考试总结]noip模拟46

    脑袋确实是不好使了需要回家暴颓治疗 数数数树鼠树 真好玩. 数数 大水题一个,妥妥的签到题目,然后... 我没签上 气展了!!! 其实我还是想麻烦了. 就是我们实际上就是排序之后每一次找头上和尾巴上的 ...

  5. NOIP模拟17.9.22

    NOIP模拟17.9.22 前进![问题描述]数轴的原点上有一只青蛙.青蛙要跳到数轴上≥

  6. NOIP 模拟4 T2

    本题属于二和一问题 子问题相互对称 考虑对于问题一:知a求b 那么根据b数组定义式 显然能发现问题在于如何求dis(最短路) 有很多算法可供选择 dijsktra,floyed,bfs/dfs,spf ...

  7. 2021.9.17考试总结[NOIP模拟55]

    有的考试表面上自称NOIP模拟,背地里却是绍兴一中NOI模拟 吓得我直接文件打错 T1 Skip 设状态$f_i$为最后一次选$i$在$i$时的最优解.有$f_i=max_{j<i}[f_j+a ...

  8. NOIP模拟赛20161022

    NOIP模拟赛2016-10-22 题目名 东风谷早苗 西行寺幽幽子 琪露诺 上白泽慧音 源文件 robot.cpp/c/pas spring.cpp/c/pas iceroad.cpp/c/pas ...

  9. contesthunter暑假NOIP模拟赛第一场题解

    contesthunter暑假NOIP模拟赛#1题解: 第一题:杯具大派送 水题.枚举A,B的公约数即可. #include <algorithm> #include <cmath& ...

随机推荐

  1. LeetCode入门指南 之 排序

    912. 排序数组 给你一个整数数组 nums,请你将该数组升序排列. 归并排序 public class Sort { //归并排序 public static int[] MergeSort(in ...

  2. centos linux下配置固定ip,方便xshell连接

    如何给centos linux设置固定ip地址,设置Linux系统的固定IP地址 首先wmware打开虚拟机 打开xshell6连接虚拟机(比较方便,这里默认设置过Linux的ip,只是不固定,每次打 ...

  3. HotSpot 对象

    概述 当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用, 并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执行相 ...

  4. MeteoInfo-Java解析与绘图教程(一)

    MeteoInfo-Java解析与绘图教程(一) 已经进入开发行业很多年了,这两年一直从事气象开发行业,为此对气象绘图有了新的见解 像色斑图与卫星图一直都有python去绘制,在偶然的情况下,我接触到 ...

  5. MapReduce框架原理-Writable序列化

    序列化和反序列化 序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储(持久化)和网络传输. 反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持久化数据,转换成内存中的 ...

  6. .Net Core with 微服务 - 分布式事务 - 2PC、3PC

    最近比较忙,好久没更新了.这次我们来聊一聊分布式事务. 在微服务体系下,我们的应用被分割成多个服务,每个服务都配置一个数据库.如果我们的服务划分的不够完美,那么为了完成业务会出现非常多的跨库事务.即使 ...

  7. 配置SSH公钥以及创建远程仓库

    一.配置SSH公钥 1.生成SSH公钥 在我们自己电脑的桌面上右键菜单,打开git命令行,输入以下命令: ssh-keygen -t rsa 一直敲回车之后,显示以下信息即表示成功生成SSH公钥,并且 ...

  8. Nginx中location匹配及rewrite重写

    目录 一.常用的Nginx正则表达式 二.location 2.1.location三类匹配类型 2.2.常用的匹配规则 2.3.location优先级 2.3.1.举例说明 2.4.实际网站使用中, ...

  9. Mysql聚合函数count(1) sum(1)结果返回0和NULL

    1.count(1) 返回为0 如果所查询的表或者where条件筛选后得到的结果集为空,则 count(1)返回为 0 如: select count(id) from test; select co ...

  10. 【原创】Spring Data Redis <=2.0.3反序列化漏洞

    Spring Data Redis隶属于Spring Data家族, 提供简单易用的方式来访问Redis缓存. Spring Data Redis在往Redis里面写数据的时候,默认会先对数据进行序列 ...