T1 树上排列

解题思路

是一个一眼切的题目。。。

看到题目第一眼就是 Deepinc 学长讲的可重集,无序 Hash 。

直接套上一颗线段树再加上树剖, \(nlog^2n\) 直接过,好像也可以树状数组维护。

但是 第二个 Subtask 的数据出锅是真的离大谱,节点不仅有 0 还有编号大于 n 的点。。。

本来是有首切的但是一开始多测没清空险些猝死。。。

由于可重集这道题给我的心理阴影我还是套了一个双 Hash ,然而类似于维护一个区间加和区间乘的做法貌似也是可以的。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=25e4+10;
int Tes,n,q,s[N];
int tim,dfn[N],id[N],topp[N],son[N],dep[N],siz[N],fa[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
struct Segment_Tree
{
ull tre[N<<2],has[N],p[N],base;
#define push_up(x) tre[x]=tre[ls]+tre[rs]
void init(int lim)
{
p[0]=1; for(int i=1;i<=lim;i++) p[i]=p[i-1]*base;
for(int i=1;i<=lim;i++) has[i]=has[i-1]+p[i];
}
void build(int x,int l,int r)
{
if(l==r) return tre[x]=p[s[id[l]]],void();
int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r);
push_up(x);
}
void update(int x,int l,int r,int pos,int val)
{
if(l==r)return tre[x]=p[val],void();
int mid=(l+r)>>1;
if(pos<=mid) update(ls,l,mid,pos,val);
else update(rs,mid+1,r,pos,val);
push_up(x);
}
ull query(int x,int l,int r,int L,int R)
{
if(L<=l&&r<=R) return tre[x];
int mid=(l+r)>>1; ull sum=0;
if(L<=mid) sum+=query(ls,l,mid,L,R);
if(R>mid) sum+=query(rs,mid+1,r,L,R);
return sum;
}
}T1,T2;
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
ull power(ull x,int y)
{
ull temp=1;
for(;y;y>>=1,x=x*x)
if(y&1) temp=temp*x;
return temp;
}
void dfs1(int x)
{
siz[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i]; if(siz[to]) continue;
fa[to]=x; dep[to]=dep[x]+1;
dfs1(to); siz[x]+=siz[to];
if(siz[to]>siz[son[x]]) son[x]=to;
}
}
void dfs2(int x,int tp)
{
topp[x]=tp; dfn[x]=++tim; id[tim]=x;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=nxt[i])
if(!dfn[ver[i]])
dfs2(ver[i],ver[i]);
}
int LCA(int x,int y)
{
while(topp[x]^topp[y])
{
if(dep[topp[x]]<dep[topp[y]]) swap(x,y);
x=fa[topp[x]];
}
if(dep[x]>dep[y]) swap(x,y); return x;
}
int dist(int x,int y){int lca=LCA(x,y);return dep[x]+dep[y]-dep[lca]-dep[fa[lca]];}
void query(int x,int y)
{
int dis=dist(x,y); ull t1=0,t2=0;
while(topp[x]^topp[y])
{
if(dep[topp[x]]<dep[topp[y]]) swap(x,y);
t1+=T1.query(1,1,n,dfn[topp[x]],dfn[x]);
t2+=T2.query(1,1,n,dfn[topp[x]],dfn[x]);
x=fa[topp[x]];
}
if(dep[x]>dep[y]) swap(x,y);
if(dfn[x]<=dfn[y])
t1+=T1.query(1,1,n,dfn[x],dfn[y]),
t2+=T2.query(1,1,n,dfn[x],dfn[y]);
if(t1==T1.has[dis]&&t2==T2.has[dis]) printf("Yes\n");
else printf("No\n");
}
void solve()
{
tot=1; tim=0; memset(head,0,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(id,0,sizeof(id));
memset(topp,0,sizeof(topp));
memset(son,0,sizeof(son));
memset(dep,0,sizeof(dep));
memset(siz,0,sizeof(siz));
memset(fa,0,sizeof(fa));
n=read(); q=read(); dep[1]=1;
for(int i=1;i<=n;i++) s[i]=read();
for(int i=1,x,y;i<n;i++)
x=read(),y=read(),
add_edge(x,y),add_edge(y,x);
dfs1(1); dfs2(1,1); T1.build(1,1,n); T2.build(1,1,n);
while(q--)
{
int opt,x,y; opt=read(); x=read(); y=read();
if(opt==1) query(x,y);
else T1.update(1,1,n,dfn[x],y),T2.update(1,1,n,dfn[x],y);
}
}
#undef int
int main()
{
#define int long long
freopen("a.in","r",stdin); freopen("a.out","w",stdout);
T1.base=13331ull; T2.base=20; T1.init(250000); T2.init(250000);
Tes=read(); while(Tes--) solve();
return 0;
}

T2 连任

解题思路

看出来是按秩合并+可撤销并茶几了,然而都不会。。。

以时间为轴,可以判断出每一条边生效的时间区间,那么直接线段树分治。

对于每个区间中的操作直接加入并进行记录,然后在处理完这个区间以及子区间之后倒序撤回操作。

递归到叶子节点直接记录答案输出就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e5+10,mod=1e9+7;
int n,m,Ans=1,fa[N],siz[N],inv[N],ans[N];
unordered_map<int,int> mp[N];
pair<int,int> s[N];
vector< pair<int,int> > v[N<<2],typ[N<<2];
inline int find(int x)
{
if(fa[x]==x) return x;
return find(fa[x]);
}
void insert(int x,int l,int r,int L,int R,pair<int,int> temp)
{
if(L<=l&&r<=R) return v[x].push_back(temp),void();
int mid=(l+r)>>1;
if(L<=mid) insert(ls,l,mid,L,R,temp);
if(R>mid) insert(rs,mid+1,r,L,R,temp);
}
void merge(int pos,int x,int y)
{
if(find(x)==find(y)) return ;
x=find(x); y=find(y); if(siz[x]>siz[y]) swap(x,y);
Ans=Ans*inv[siz[x]]%mod*inv[siz[y]]%mod*(siz[x]+siz[y])%mod;
siz[y]+=siz[x]; fa[x]=y; typ[pos].push_back(make_pair(x,y));
}
void work_back(int pos)
{
for(int i=(int)typ[pos].size()-1;i>=0;i--)
{
int x=typ[pos][i].first,y=typ[pos][i].second;
Ans=Ans*inv[siz[y]]%mod; fa[x]=x; siz[y]-=siz[x];
Ans=Ans*siz[x]%mod*siz[y]%mod;
}
}
void solve(int x,int l,int r)
{
for(auto it:v[x]) merge(x,it.first,it.second);
if(l==r) return ans[l]=Ans,work_back(x),void();
int mid=(l+r)>>1; solve(ls,l,mid); solve(rs,mid+1,r);
work_back(x);
}
#undef int
int main()
{
#define int long long
freopen("b.in","r",stdin); freopen("b.out","w",stdout);
n=read(); m=read(); inv[1]=fa[1]=siz[1]=1;
for(int i=2;i<=n;i++) fa[i]=i,siz[i]=1,inv[i]=inv[mod%i]*(mod-mod/i)%mod;
for(int i=1,opt,x,y;i<=m;i++)
{
opt=read(); x=read(); y=read();
if(x>y) swap(x,y); s[i]=make_pair(x,y);
if(opt==1){mp[x].insert(make_pair(y,i));continue;}
insert(1,1,m,mp[x].find(y)->second,i-1,s[i]);
mp[x].erase(y);
}
for(int i=1;i<=m;i++)
if(mp[s[i].first].find(s[i].second)!=mp[s[i].first].end())
insert(1,1,m,mp[s[i].first].find(s[i].second)->second,m,s[i]);
solve(1,1,m); for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}

T3 排列

解题思路

sort -> 桶排 竟然会快 0.2s 实在是有用的卡常小技巧。。

第一感觉肯定是三维偏序,但是这个东西实际上是可以转化为二维上的!!!

假设一个三元排列 \((a,b,c)\) 满足三维偏序的个数可以通过两两之间的满足二维偏序的对数求出来。

设 \((a,b),(b,c),(a,c)\) 一共有 \(M\) 对合法的二维偏序对,那么一个对 \((i,j)\) 要么被计算一次要么被计算三次。

于是满足三维偏序的对数就是 \(\dfrac{M-\frac{n\times(n-1)}{2}}{2}\) 。

那么所有已经确定的数之间的贡献就有了,我们可以对于每一个已经确定的数字计算出它前后的 -1 的期望贡献。

同时也要算一下 -1 与 -1 之间的贡献。

code

#include<bits/stdc++.h>
// #define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e6+10,mod=998244353;
int n,cnt,ans,inv,inv2,sta[N];
struct Node{int a,b,c;}s[N],t[N];
bool vis[N];
struct BIT
{
int tre[N];
#define lowbit(x) (x&(-x))
inline void clear(){memset(tre,0,sizeof(tre));}
inline void insert(register int x){for(register int i=x;i<=n;i+=lowbit(i))tre[i]++;}
inline int query(register int x){register int sum=0;for(register int i=x;i;i-=lowbit(i))sum+=tre[i];return sum;}
}T;
inline int power(register int x,register int y,register int p=mod)
{
int temp=1;
for(;y;y>>=1,x=1ll*x*x%p)
if(y&1) temp=1ll*temp*x%p;
return temp;
}
#define add(x,y) x=(x+y)%mod
inline bool comp(Node x,Node y){return x.b<y.b;}
// #undef int
int main()
{
// #define int long long
freopen("c.in","r",stdin); freopen("c.out","w",stdout);
n=read(); inv2=(mod+1)>>1; for(register int i=1,x,y;i<=n;i++) s[i].b=read();
for(register int i=1;i<=n;i++) s[i].a=i,s[i].c=read(),vis[s[i].c==-1?0:s[i].c]=true;
for(register int i=1;i<=n;i++) T.insert(s[i].b),add(ans,T.query(s[i].b-1)); T.clear();
for(register int i=1;i<=n;i++) if(!vis[i]) sta[++cnt]=i; inv=power(cnt,mod-2);
for(register int i=1,tot=0;i<=n;i++)
{
if(s[i].c!=-1) T.insert(s[i].c),add(ans,T.query(s[i].c-1));
register int pos=upper_bound(sta+1,sta+cnt+1,s[i].c)-sta-1;
if(s[i].c!=-1) add(ans,1ll*pos*inv%mod*tot%mod),add(ans,1ll*(cnt-tot)*inv%mod*(cnt-pos)%mod);
else add(ans,1ll*tot*inv2%mod); tot+=s[i].c==-1;
}
T.clear(); for(int i=1;i<=n;i++) t[s[i].b]=s[i];
for(register int i=1,tot=0;i<=n;i++)
{
if(t[i].c!=-1) T.insert(t[i].c),add(ans,T.query(t[i].c-1));
register int pos=upper_bound(sta+1,sta+cnt+1,t[i].c)-sta-1;
if(t[i].c!=-1) add(ans,1ll*pos*inv%mod*tot%mod),add(ans,1ll*(cnt-tot)*inv%mod*(cnt-pos)%mod);
else add(ans,1ll*tot*inv2%mod); tot+=t[i].c==-1;
}
printf("%lld",1ll*(1ll*ans-1ll*(n-1)*n%mod*inv2%mod+mod)%mod*inv2%mod);
return 0;
}

T4 追逐

解题思路

一个最优的策略显然是把 qjd 逼到一个叶子节点之后,把之后需要的操作全部做了再把他放出来。

那么我们需要计算出每一个节点一开始向子树中走然后绕过子树一圈之后最后回到该节点并且下一步将要走到父亲节点的最小操作次数。

这个东西直接记录一个最大值次大值树形 DP 转移即可。

显然只会让 qjd 到向下走一次,于是我们可以二分出一个最后答案。

对于一个可能的答案值 \(mid\) 计算一下当前可以操作的步数也就是多余的操作步数,如果当前节点的一个儿子的 DP 值加上之前的操作值还有祖先要堵住的节点是大于 \(mid\) 的话,这个儿子就是不合法的。

我们显然是要利用多余的操作步数将这些儿子堵住的,如果不够的话也是不合法的。

最后再判一下加上最大值的子树之后总操作数是否合法就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e6+10;
int n,root,ending,f[N],fa[N],du[N],bas[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
bool vis[N];
void add_edge(int x,int y)
{
ver[++tot]=y; du[y]++;
nxt[tot]=head[x]; head[x]=tot;
}
void dfs(int x)
{
int mx=0,sec=0;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i]; if(to==fa[x]) continue;
fa[to]=x; dfs(to);
if(mx<f[to]) sec=mx,mx=f[to];
else sec=max(sec,f[to]);
}
f[x]=sec+du[x]-1+(!fa[x]);
}
void dfs2(int x)
{
if(x==root) bas[x]=0; else bas[x]+=du[x];
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i]; if(to==fa[x]) continue;
bas[to]+=bas[x]; dfs2(to);
}
}
bool check(int val)
{
int now=ending,use=0,hav=1,mx=0;
while(now!=root)
{
int cnt=0;
for(int i=head[now];i;i=nxt[i])
if(ver[i]!=fa[now]&&!vis[ver[i]])
if(use+f[ver[i]]+bas[now]>val) cnt++;
else mx=max(mx,f[ver[i]]);
if(hav<cnt) return false;
use+=cnt; hav-=cnt; now=fa[now]; hav++;
}
return use+mx<=val;
}
#undef int
int main()
{
#define int long long
freopen("d.in","r",stdin); freopen("d.out","w",stdout);
n=read(); root=read(); ending=read();
for(int i=1,x,y;i<n;i++)
x=read(),y=read(),
add_edge(x,y),add_edge(y,x);
dfs(root); int x=ending; while(x){vis[x]=true;x=fa[x];}
for(int i=1;i<=n;i++)
if(vis[i])
for(int j=head[i];j;j=nxt[j])
bas[ver[j]]--;
int l=0,r=n,ans; dfs2(root);
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
printf("%lld",ans); return 0;
}

NOIP模拟96的更多相关文章

  1. NOIP模拟96(多校29)

    T1 子集和 解题思路 大概是一个退背包的大白板,然而我考场上想复杂了,竟然还用到了组合数. 但是大概意思是一样的,有数的最小值一定是一个在 \(a\) 数组中存在的数字. 那么我们想办法除去它对应的 ...

  2. NOIP 模拟4 T2

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

  3. NOIP模拟赛20161022

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

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

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

  5. NOIP模拟赛 by hzwer

    2015年10月04日NOIP模拟赛 by hzwer    (这是小奇=> 小奇挖矿2(mining) [题目背景] 小奇飞船的钻头开启了无限耐久+精准采集模式!这次它要将原矿运到泛光之源的矿 ...

  6. 大家AK杯 灰天飞雁NOIP模拟赛题解/数据/标程

    数据 http://files.cnblogs.com/htfy/data.zip 简要题解 桌球碰撞 纯模拟,注意一开始就在袋口和v=0的情况.v和坐标可以是小数.为保险起见最好用extended/ ...

  7. 队爷的讲学计划 CH Round #59 - OrzCC杯NOIP模拟赛day1

    题目:http://ch.ezoj.tk/contest/CH%20Round%20%2359%20-%20OrzCC杯NOIP模拟赛day1/队爷的讲学计划 题解:刚开始理解题意理解了好半天,然后发 ...

  8. 队爷的Au Plan CH Round #59 - OrzCC杯NOIP模拟赛day1

    题目:http://ch.ezoj.tk/contest/CH%20Round%20%2359%20-%20OrzCC杯NOIP模拟赛day1/队爷的Au%20Plan 题解:看了题之后觉得肯定是DP ...

  9. 队爷的新书 CH Round #59 - OrzCC杯NOIP模拟赛day1

    题目:http://ch.ezoj.tk/contest/CH%20Round%20%2359%20-%20OrzCC杯NOIP模拟赛day1/队爷的新书 题解:看到这题就想到了 poetize 的封 ...

  10. CH Round #58 - OrzCC杯noip模拟赛day2

    A:颜色问题 题目:http://ch.ezoj.tk/contest/CH%20Round%20%2358%20-%20OrzCC杯noip模拟赛day2/颜色问题 题解:算一下每个仆人到它的目的地 ...

随机推荐

  1. docker 应用篇————tomcat例子[七]

    前言 虽然我干的事情和java不多,但是例子是为了熟悉原理,而不是为了例子而例子的,故而整理一下tomcat的例子. 正文 使用官方示例: 然后运行一下. 没有找到然后进行下载了. 可以看到这里就已经 ...

  2. python读取文件时,删除重复行并计数

    from collections import Counterwith open('a.txt', 'r+') as f: a = f.readlines() for i in range(len(a ...

  3. 2FA(双因素身份验证)之手机令牌(TOTP)逻辑

    2FA(双因素身份验证)之手机令牌(TOTP)逻辑 纯猜测,没试过,有空试 分为移动端.客户端以及网页端 Steam那种属于APP是网页,客户端是网页,网页端也是网页,挺抽象的 关键点: 时间一致(时 ...

  4. Oracle的主键id自增

    Oracle的主键id自增 可以直接用序列加触发器的方式实现 首先表里面要有个主键,没有的话用语句或者在编译器中加一下,都可以 然后创建一个序列,一般来说最常用的有这几个参数 CREATE SEQUE ...

  5. OpenYurt v1.1.0: 新增 DaemonSet 的 OTA 和 Auto 升级策略

    简介: 在 OpenYurt v1.1.0 版本中,我们提供了 Auto 和 OTA 的升级策略.Auto 的升级策略重点解决由于节点 NotReady 而导致 DaemonSet升级阻塞的问题,OT ...

  6. 关于Kubernetes规划的灵魂n问

    Kubernetes已经成为企业新一代云IT架构的重要基础设施,但是在企业部署和运维Kubernetes集群的过程中,依然充满了复杂性和困扰.阿里云容器服务自从2015年上线后,一路伴随客户和社区的成 ...

  7. [FAQ] Pytorch PytorchStreamReader failed reading zip archive

    比如:rm -rf ~/.cache/huggingface Tool:ChatAI Link:https://www.cnblogs.com/farwish/p/17290240.html

  8. [PHP] Laravel 的 503 Service Unavailable 模板提示的来源

    当我们看到 Laravel 的 503 样式的模板时,是启用维护模式的时候的提示(php artisan down). 开启访问运行 php artisan up. Refer:Laravel 503 ...

  9. [K8s] Kubernetes核心基础概念 Node, Pod, ReplicaSet, Deployment, Service, Ingress, ConfigMap

    Node 即 Kubernetes 集群中的一台工作机器,物理机或者虚拟机. https://kubernetes.io/zh/docs/concepts/architecture/nodes/ 通常 ...

  10. [ST] 音悦Tai 凉了,一段印记成为过去时

    互联网上依旧流传着音悦台的传说,过去十年间,你我也许都曾是音悦台的用户. 很多MV的右上角依然是 YinYueTai 的 Logo,比如 Siren-宣美,算是一个时代的印记吧. 互联网企业,即便是真 ...