加载中…

返回上一页

NOIP模拟4

下发文件



O
 I










树上排列

这个东西就很妙,赛时没看出来是原题,但是用了一个哈希的思路切了.

先把原树剖一下,用来求后面的 lca 和统计答案(倍增也行,依个人喜好).

暴力的思路就是求两个点的 lca,暴力把每个点都遍历一遍.

发现这玩意很像单点修改、区间查询,那么就把它能往线段树上凑的就试着凑一凑. 在预处理的时候把每个重链的从上到下每个点记录一下,就相当于把每个点重新编号,用以后面的查询.

然后建树的时候把每个点存一个哈希值. 如果直接存本身的话不行,比如 1 + 3 = 4,而 2 + 2 也等于 4,这样一求和就说不清是否有点 1 和 3 了. 我这里用的 x × (x + 4).

查询的时候,利用树剖求 lca 的特点不断跳重链(倍增一样,只不过是跳父亲),因为刚才重新编号了一下,这样就能直接查询当前点到重链顶的所有值的加和.

由于跳重链是 log 的,且线段树是 log 的,所以复杂度是 O(n log n + q log2 n),时限 4s,足以通过本题.

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define ull unsigned long long
#define pll pair<ll,ll>
#define maxn 250001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
inline ll read()
{
	rg bool f=0;rll x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar(); return f?-x:x;
}
inline void write(rll x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0'); }
struct node
{
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
	ull v;
}t[maxn<<2];
ll T,n,q,x,y,a[maxn],p[maxn],id[maxn],tot;
vector<ll> g[maxn],b[maxn];
ll f[maxn],d[maxn],sz[maxn],son[maxn],dfn[maxn],top[maxn],cnt;
ull hs[maxn];
#define pushup(rt) t[rt].v=t[ls(rt)].v+t[rs(rt)].v
inline void build(rll rt,rll l,rll r)
{
	if(l==r) { t[rt].v=(ull)a[id[l]]*(a[id[l]]+4); /*printf("pos=%lld id=%lld v=%lld\n",l,id[l],a[l]);*/ return; }
	rll mid=(l+r)>>1; build(ls(rt),l,mid); build(rs(rt),mid+1,r); pushup(rt);
}
inline void upd(rll rt,rll l,rll r,rll pos,rll v)
{
	if(l==r) { t[rt].v=(ull)v*(v+4); return; } rll mid=(l+r)>>1;
	if(pos<=mid) upd(ls(rt),l,mid,pos,v); else upd(rs(rt),mid+1,r,pos,v); pushup(rt);
}
inline ull query(rll rt,rll l,rll r,rll x,rll y)
{
	if(x<=l&&r<=y) return t[rt].v; rll mid=(l+r)>>1; rg ull ans=0;
	if(x<=mid) ans+=query(ls(rt),l,mid,x,y); if(y>mid) ans+=query(rs(rt),mid+1,r,x,y); return ans;
}
inline void dfs1(rll x,rll fa)
{
	f[x]=fa;d[x]=d[fa]+1;sz[x]=1;son[x]=0;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i];if(to==fa) continue; dfs1(to,x);sz[x]+=sz[to];if(sz[to]>sz[son[x]]) son[x]=to;
	}
}
inline void dfs2(rll x,rll fa)
{
	// cout<<x<<'*'<<fa<<endl;
	dfn[x]=++cnt;top[x]=fa;b[fa].emplace_back(x);if(son[x]) dfs2(son[x],fa);
	for(rll i=0;i<g[x].size();i++) { rll to=g[x][i];if(dfn[to]) continue;dfs2(to,to); }
}
inline ull getd(rll x,rll y)
{
	rg ull ans=0;// cout<<x<<'*'<<y<<endl;
	while(top[x]^top[y])
	{
		if(d[top[x]]<d[top[y]]) swap(x,y);
		ans+=query(1,1,n,p[top[x]],p[x]);
		// printf("x=%lld y=%lld ans=%llu\n",top[x],x,ans);
		x=f[top[x]];
	}
	if(d[x]<d[y]) ans+=query(1,1,n,p[x],p[y]); else ans+=query(1,1,n,p[y],p[x]);
	// printf("x=%lld y=%lld ans=%llu\n",x,y,ans);
	return ans;
}
inline ll lca(rll x,rll y)
{
	while(top[x]^top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); x=f[top[x]]; }
	if(d[x]<d[y]) return x; return y;
}
int main()
{
	freopen("a.in","r",stdin); freopen("a.out","w",stdout);
	for(rll i=1;i<maxn;i++) hs[i]=hs[i-1]+i*(i+4);
	T=read(); while(T--)
	{
		n=read();q=read(); for(rll i=1;i<=n;i++) g[i].clear(),b[i].clear(),a[i]=read();
		for(rll i=1,u,v;i<n;i++) u=read(),v=read(),g[u].emplace_back(v),g[v].emplace_back(u);
		memset(t,0,sizeof(t)); memset(dfn,0,sizeof(dfn)); cnt=tot=0; dfs1(1,0);dfs2(1,1);
		for(rll i=1;i<=n;i++) if(!b[i].empty()) { for(rll j=0;j<b[i].size();j++) p[b[i][j]]=++tot,id[tot]=b[i][j]; }
		build(1,1,n);
		while(q--) switch(read())
		{
		case 1:x=read();y=read();
			// printf("dis=%lld hs=%llu\n",d[x]+d[y]-(d[lca(x,y)]<<1)+1,hs[d[x]+d[y]-(d[lca(x,y)]<<1)+1]);
			puts(getd(x,y)==hs[d[x]+d[y]-(d[lca(x,y)]<<1)+1]?"Yes":"No");break;
		case 2:x=read();y=read();upd(1,1,n,p[x],y);/*printf("%lld pos=%lld\n",x,p[x]);*/break;
		}
	}
	return 0;
}

连任

赛时发现这是原题,但是忘了是哪道题了. 结果这个题居然还爆了!如果这个题能 A 就前 15 了!原题是地理课.

没写过这题的题解,就写一个回顾一下.

维护一棵线段树,每个节点开一个 vector,代表要修改的边. 一个节点代表一个区间,因此对于一个区间直接进行操作就保证了复杂度.

在连边的时候,用一个可撤销并查集,在统计答案的时候先递归左子树右子树,将需要连的边模拟连上(就用并查集实现)并且更新答案. 然后再进行撤销操作并将当前计算出的结果(答案)也进行撤销.

然后应该就没什么了.

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define pll pair<ll,ll>
#define maxn 100001
#define mod 1000000007
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
inline ll read()
{
	rg bool f=0;rll x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar(); return f?-x:x;
}
inline void write(rll x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0'); }
struct node { ll x,to,st,ed; }e[maxn];
struct tree
{
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
	vector<ll> g;
}t[maxn<<2];
struct nd { ll x,f1,f2,sz; }; vector<nd> h;
ll n,m,num=1,f[maxn],sz[maxn],ans[maxn];
vector<ll> g[maxn];
unordered_map<ll,ll> mp;
inline ll ksm(rll a,rll b) { rll ans=1;a%=mod; for(rll i=b;i;i>>=1) { if(i&1) (ans*=a)%=mod; (a*=a)%=mod; } return ans; }
inline ll find(rll x) { if(x^f[x]) return find(f[x]); return f[x]; }
inline void pushup(rll rt)
{
	for(rll i=(ll)h.size()-1;~i;i--)
	{
		if(h[i].x^rt) break; (num*=ksm(sz[h[i].f2],mod-2))%=mod; f[h[i].f1]=h[i].f1;
		sz[h[i].f2]-=(sz[h[i].f1]=h[i].sz),(num*=sz[h[i].f1]*sz[h[i].f2]%mod)%=mod; h.pop_back();
	}
}
inline void upd(rll rt,rll l,rll r,rll x,rll y,rll v)
{
	if(x<=l&&r<=y) { t[rt].g.emplace_back(v); return; } rll mid=(l+r)>>1;
	if(x<=mid) upd(ls(rt),l,mid,x,y,v); if(y>mid) upd(rs(rt),mid+1,r,x,y,v);
}
inline void query(rll rt,rll l,rll r)
{
	for(rll i=0;i<t[rt].g.size();i++)
	{
		rll to=t[rt].g[i],f1=find(e[to].x),f2=find(e[to].to);
		if(f1^f2)
		{
			(num*=ksm(sz[f1],mod-2)*ksm(sz[f2],mod-2)%mod*(sz[f1]+sz[f2])%mod)%=mod;
			if(sz[f1]<sz[f2]) h.push_back((nd) { rt,f1,f2,sz[f1] }),f[f1]=f2,sz[f2]+=sz[f1],sz[f1]=0;
			else h.push_back((nd) { rt,f2,f1,sz[f2] }),f[f2]=f1,sz[f1]+=sz[f2],sz[f2]=0;
		}
	}
	if(l==r) { ans[l]=num; pushup(rt); return; } rll mid=(l+r)>>1;
	query(ls(rt),l,mid); query(rs(rt),mid+1,r); pushup(rt);
}
int main()
{
	freopen("b.in","r",stdin); freopen("b.out","w",stdout);
	n=read();m=read(); for(rll i=1;i<=n;i++) f[i]=i,sz[i]=1; fprintf(stderr,"%lld %lld\n",n,m);
	for(rll i=1,op;i<=m;i++)
	{
		op=read();e[i].x=read();e[i].to=read(); if(e[i].x>e[i].to) swap(e[i].x,e[i].to);
		if(op==1) e[mp[e[i].x*100000+e[i].to]=i].st=i; else e[mp[e[i].x*100000+e[i].to]].ed=i-1;
		fprintf(stderr,"%lld %lld %lld ",op,e[i].x,e[i].to);
	}
	for(rll i=1;i<=m;i++) { if(!e[i].ed) e[i].ed=m; if(e[i].st&&e[i].ed) upd(1,1,m,e[i].st,e[i].ed,i); }
	query(1,1,m); for(rll i=1;i<=m;i++) assert(ans[i]>0),write(ans[i]),putn;
	return 0;
}

排列

也是很妙的一题.

假设现在有三个数列 a , b , c,那么要求有多少对 (i , j) 使得满足 ai < ajbi < bjci < cj.

很明显的三维偏序,可以用 cdq 分治做. 这里介绍一种使用树状数组的做法.

先用 bit 求出三个数列两两组合的二维偏序对正序对. 发现一个巧妙的性质:一对 (i , j) 要么会被统计一次,要么会被统计三次. 因为是偏序关系的位置数要么是两个,要么是三个.

那么,设 sum 为二维偏序对数,答案就是:\frac{sum-\frac{n\left(n-1\right)}{2}}{2}.

对于有 -1 的情况,分两部分:

  1. -1 和其它数字

    对左半部分的贡献是比它小的未使用的数的数量(add)与左边位置数量(sum)的乘积. 当然,是期望,别忘了除以总空位数(cnt),用式子描述就是:ans+=\frac{add\times sum}{cnt}. 对右边部分的就是正好相反.

  2. -1-1

    每两个数之间要么大于要么小于,所以是二分之一的概率. 组合一下就是 \frac{1}{2}C_{cnt}^2=\frac{cnt\times (cnt-1)}{4}. 由于上面提到的两两组合要计算两次,再乘 2 即可.

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define pll pair<ll,ll>
#define maxn 1000001
#define mod 998244353
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
inline ll read()
{
	rg bool f=0;rll x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar(); return f?-x:x;
}
inline void write(rll x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0'); }
class bit
{
private:
#define lowbit(x) (x&-x)
	ll c[maxn];
public:
	inline void clear() { memset(c,0,sizeof(c)); }
	inline void update(rll x,rll n,rll v) { for(rll i=x;i<=n;i+=lowbit(i)) c[i]+=v; }
	inline ll query(rll x) { rll ans=0; for(rll i=x;i;i-=lowbit(i)) ans+=c[i]; return ans; }
}t;
struct node
{
	ll a,b;
	inline friend bool operator<(rg node a,rg node b) { return a.a<b.a; }
}a[maxn];
ll n,cnt,sum,ans,add[maxn];
vector<ll> g;
inline ll ksm(rll a,rll b) { rll ans=1;a%=mod; for(rll i=b;i;i>>=1) { if(i&1) (ans*=a)%=mod; (a*=a)%=mod; } return ans; }
int main()
{
	freopen("c.in","r",stdin); freopen("c.out","w",stdout);
	n=read(); for(rll i=1;i<=n;i++) a[i].a=read(); for(rll i=1;i<=n;i++) if(~(a[i].b=read())) add[a[i].b]=1; else cnt++;
	for(rll i=1;i<=n;i++) add[i]^=1;
	for(rll i=1;i<=n;i++) add[i]+=add[i-1]; for(rll i=1;i<=n;i++) ans+=t.query(a[i].a),t.update(a[i].a,n,1);
	t.clear(); for(rll i=1;i<=n;i++)
		if(~a[i].b) (ans+=t.query(a[i].b)+add[a[i].b]*sum%mod*ksm(cnt,mod-2)%mod+(cnt-add[a[i].b])*(cnt-sum)%mod*ksm(cnt,mod-2)%mod)%=mod,
			t.update(a[i].b,n,1); else sum++;
	sum=0; sort(a+1,a+n+1); t.clear(); for(rll i=1;i<=n;i++)
		if(~a[i].b) (ans+=t.query(a[i].b)+add[a[i].b]*sum%mod*ksm(cnt,mod-2)%mod+(cnt-add[a[i].b])*(cnt-sum)%mod*ksm(cnt,mod-2)%mod)%=mod,
			t.update(a[i].b,n,1); else sum++;
	write((ans+(cnt*(cnt-1)>>1)%mod-(n*(n-1)%mod*ksm(2,mod-2)%mod)+mod)*ksm(2,mod-2)%mod);
	return 0;
}

追逐

还是非常有趣.

发现这个最优的策略一定是妹子把 qjd 逼近到一个叶子节点,然后堵死,将能走的其它路封上,使 qjd 只能往 t 走.

考虑如何求出这条路. 由于 qjd 想走更长的路,所以一定会向能操作更多次数的子树(连边较多的点)钻. 这个东西拿一个树形 dp 求就好了. 注意要求次大值(因为可以堵一条路).

然后,操作多少次是否合法,显然单调,二分去找到这个次数. 查找时,如果要删掉的子树数目大于能操作次数的话,就需要删除这个子树(截断). 如果仍无法删除(还是超了次数),就不合法.

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define pll pair<ll,ll>
#define maxn 1000001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
inline ll read()
{
	rg bool f=0;rll x=0;rg char ch=getchar();while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar(); return f?-x:x;
}
inline void write(rll x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0'); }
ll n,t,s,x,l,r,ans,f[maxn],dp[maxn],nx[maxn],pre[maxn];
vector<ll> g[maxn];
inline void dfs(rll x,rll fa)
{
	rll zd=0,cd=0; f[x]=fa;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i]; if(to==fa) continue; dfs(to,x);
		if(dp[to]>=zd) cd=zd,zd=dp[to]; else if(dp[to]>cd) cd=dp[to];
	}
	dp[x]=g[x].size()+cd-1;
}
inline bool chk(rll mid)
{
	rll x=s,cnt=1,sum=0;
	while(x^t)
	{
		rll k=0; for(rll i=0;i<g[x].size();i++)
		{
			rll to=g[x][i]; if(to==nx[x]||to==f[x]) continue; if(dp[to]+pre[x]+sum>mid) k++;
		}
		sum+=k; if(sum>cnt||sum>mid) return 0; cnt++;x=f[x];
	}
	return 1;
}
int main()
{
	freopen("d.in","r",stdin); freopen("d.out","w",stdout);
	n=read();t=read();s=read(); for(rll i=1,u,v;i<n;i++) g[u=read()].emplace_back(v=read()),g[v].emplace_back(u);
	dfs(t,0); x=s; while(x^t) nx[f[x]]=x,x=f[x];
	x=nx[t]; while(x^s) pre[x]=pre[f[x]]+g[x].size()-1-1,x=nx[x]; pre[x]=pre[f[x]]+g[x].size()-1;
	l=0,r=1000000000000; while(l<=r) { rll mid=l+r>>1; if(chk(mid)) ans=mid,r=mid-1; else l=mid+1; }
	write(ans);
	return 0;
}

总结

做题不改错,原题两行泪

posted @ 2022-11-16 19:22  1Liu  阅读(36)  评论(1编辑  收藏  举报