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. redis 简单整理——CEO[十五]

    前文 简单介绍一下CEO. 正文 Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信 息用来实现诸如附近位置.摇一摇这类依赖于地理位置信息的功能,对于需 要实现这些功能的开发者来 ...

  2. redis 简单整理——redis 的哈希基本结构和命令[三]

    前言 简单介绍一下哈希基本结构和命令. 正文 什么是hash呢? hash也可以叫做字典.关联数组. 哈希类型是键本身又是一个键值对结构: value={{field1,value1},...{fie ...

  3. 重新点亮linux 命令树————进程的控制[二十二]

    前言 简单整理一下进程的控制 正文 进程优先级 nice 从-20-19,值越小,优先级越高 renice 从新设置优先级 进程的作业控制: job 那么先来创建一个进程. 那么使用top -p 26 ...

  4. ABP -Vnext框架一步一步入门落地教程——ABP Vnext框架代码安装和启动(一)

    兄弟们,人生需要指引,而复制是成功最快的方式,让我们开始行动吧 --codesoft 教程介绍 ABP-Vnext框架我们之前摸了无数次,好象初恋的女孩,一直在靠近,一直在努力,一直不敢盯着她的眼睛说 ...

  5. 《c#高级编程》第5章C#5.0中的更改(十一)——字符串插值

    在 C# 5 中,引入了字符串插值(string interpolation)语法,它提供了一种简单.直观的方式来将变量的值嵌入到字符串中.在以前的版本中,我们需要使用字符串格式化功能来实现这个目的, ...

  6. boltdb 介绍

    介绍 BoltDB 是一个用 Go 语言编写的嵌入式键/值数据库.以下是关于 BoltDB 的一些基本介绍: 键/值存储: BoltDB 为应用程序提供了简单的键/值存储接口. 事务: BoltDB ...

  7. Gartner APM 魔力象限技术解读——全量存储? No! 按需存储?YES!

    简介: 在云原生时代,充分利用边缘节点的计算和存储能力,结合冷热数据分离实现高性价比的数据价值探索已经逐渐成为 APM 领域的主流. 作者:夏明(涯海) 调用链记录了完整的请求状态及流转信息,是一座巨 ...

  8. WPF 多线程下跨线程处理 ObservableCollection 数据

    本文告诉大家几个不同的方法在 WPF 里,使用多线程修改或创建 ObservableCollection 列表的数据 需要明确的是 WPF 框架下,非 UI 线程直接或间接访问 UI 是不合法的,设计 ...

  9. WPF 已知问题 BitmapDecoder.Create 不支持传入 Asynchronous 的文件流

    这是在 GitHub 上有小伙伴报的问题,在 WPF 中,不支持调用 BitmapDecoder.Create 方法,传入的 FileStream 是配置了 FileOptions.Asynchron ...

  10. WPF 解决 Skia 因为找不到字体而绘制不出中文字符

    在 WPF 使用 Skia 做渲染工具,如果绘制的中文都是方块,也许是字体的问题.字体的问题是 Skia 没有找到字体,本文告诉大家如何修复 在 Skia 使用特定字体,可以使用 SkiaSharp ...