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 < aj 且 bi < bj 且 ci < cj.
很明显的三维偏序,可以用 cdq 分治做. 这里介绍一种使用树状数组的做法.
先用 bit 求出三个数列两两组合的二维偏序对正序对. 发现一个巧妙的性质:一对 (i , j) 要么会被统计一次,要么会被统计三次. 因为是偏序关系的位置数要么是两个,要么是三个.
那么,设 sum 为二维偏序对数,答案就是:.
对于有 -1
的情况,分两部分:
-
-1
和其它数字对左半部分的贡献是比它小的未使用的数的数量(add)与左边位置数量(sum)的乘积. 当然,是期望,别忘了除以总空位数(cnt),用式子描述就是:. 对右边部分的就是正好相反.
-
-1
和-1
每两个数之间要么大于要么小于,所以是二分之一的概率. 组合一下就是 . 由于上面提到的两两组合要计算两次,再乘 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;
}
总结:
做题不改错,原题两行泪
--END--
我的博客: 𝟷𝙻𝚒𝚞
本文链接: https://www.cnblogs.com/1Liu/p/16897236.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!