10.21 牛客提高集训营6
2018.10.21 牛客提高集训营6
不是很懂那些粘人代码还直接交上去的人,在提交记录里很好看么?
A 最长路(拓扑 分层)
容易想到建反图拓扑。有了最长路后,按最长路对图分层。
因为当前点路径字典序最小,就是要满足第一条边最小后,再满足下一个点路径字典序最小。后者可以直接用上一层的答案。
按这两个关键字排序,就可以得到当前层的路径字典序排名了。
复杂度\(O(n\log n)\)。
比较两条路径字典序大小,因为处理到\(x\)时,前面的转移都确定了,所以也可以倍增+Hash找出第一个不同字符。。
//505ms 78300KB
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
#define mp std::make_pair
#define pr std::pair<int,int>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define mod 998244353
#define Mod(x) x>=mod&&(x-=mod)
typedef long long LL;
const int N=1e6+5,INF=2e9;
int dgr[N];
pr A[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Graph
{
int Enum,H[N],nxt[N],to[N],len[N];
void AE(int u,int v,int w)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
}
}G,R;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
inline bool cmp(int a,int b)
{
return A[a]<A[b];
}
void Toposort(int n,int m)
{
static int q[N],f[N],rk[N],sum[N];
static bool vis[N];
static std::vector<int> v[N];
int h=0,t=0;
for(int i=1; i<=n; ++i) if(!dgr[i]) q[++t]=i;
while(h<t)
{
int x=q[++h];
vis[x]=1, v[f[x]].push_back(x), A[x]=mp(INF,0);
for(int i=R.H[x],v; i; i=R.nxt[i])
{
f[v=R.to[i]]=std::max(f[v],f[x]+1);
if(!--dgr[v]) q[++t]=v;
}
}
for(int i=1; i<=n; ++i)
{
if(!v[i].size()) break;
int l=v[i].size();
std::vector<int> &vi=v[i];
for(int j=0; j<l; ++j)
{
int x=vi[j]; pr t;
for(int k=G.H[x],v; k; k=G.nxt[k])
if(f[x]==f[v=G.to[k]]+1 && (t=mp(G.len[k],rk[v]))<A[x])
A[x]=t, sum[x]=29ll*sum[v]%mod+t.first, Mod(sum[x]);
}
std::sort(vi.begin(),vi.end(),cmp);
for(int j=0; j<l; ++j) rk[vi[j]]=j;
}
for(int i=1; i<=n; ++i)
if(vis[i]) printf("%d\n",(int)(29ll*sum[i]%mod));
else puts("Infinity");
}
int main()
{
int n=read(),m=read();
for(int i=1,u,v,w; i<=m; ++i)
u=read(),v=read(),w=read(),++dgr[u],G.AE(u,v,w),R.AE(v,u,w);
Toposort(n,m);
return 0;
}
B 选择题(DP 期望 退背包)
第\(x\)个人的期望得分为:
\(cx\)表示\(x\)选了哪个选项,\(p(x,mx,cx)\)表示在\(cnt(mx)>\lfloor\frac n2\rfloor\)时,\(x\)选了\(cx\)的概率。
那么对\(cx\)分类讨论,\(f(x,mx,cnt)\)表示除\(x\)外,恰好有\(cnt\)个人选了\(mx\)的概率。则有
\(f(x,mx,cnt)\)的转移就是个背包。枚举\(x\),就可以\(O(n^3)\)了。期望得分\(55\)。
我们发现\(x\)变成\(x+1\)时,对\(f\)的影响时,加入一个元素\(x\)并删掉\(x+1\)的影响。先拿所有人算一遍\(f\),加入一个人就是做一遍\(O(n)\)的DP。把\(f\)的转移反过来就可以\(O(n)\)删掉\(x\)的贡献了:
\(j\)为\(0\)的时候特判。注意\(p\)为\(1\)的时候也要特判。
复杂度\(O(n^2)\)。
//226ms 536KB
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define mod 998244353
#define Mod(x) x>=mod&&(x-=mod)
typedef long long LL;
const int N=2005;
int P[N][4],W[4][4],f[N],g[N];
LL Ans[N];
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
inline int Inv(int x,int k=mod-2)
{
int t=1;
for(; k; k>>=1,x=1ll*x*x%mod)
if(k&1) t=1ll*t*x%mod;
return t;
}
void Solve(int n,int mx)
{
memset(f,0,sizeof f);
f[0]=1;
for(int i=1; i<=n; ++i)
{
LL p=P[i][mx];
for(int j=i; j; --j)
f[j]=p*f[j-1]%mod+(1-p)*f[j]%mod, Mod(f[j]);//就是(mod+)1-p啊(mod-p是-p)
f[0]=(1-p)*f[0]%mod;
}
for(int i=1; i<=n; ++i)//x
{
LL p=P[i][mx];
if(p!=1)
{
LL inv=Inv(mod+1-p);
g[0]=inv*f[0]%mod;
for(int j=1; j<=n; ++j) g[j]=inv*(f[j]-p*g[j-1]%mod)%mod;
}
else
{
for(int j=0; j<n; ++j) g[j]=f[j+1];//f[i-1][j-1]=f[i][j]
g[n]=0;
}
LL tmp=0;
for(int j=(n>>1)+1; j<=n; ++j) tmp+=g[j];
tmp%=mod;
for(int j=0; j<4; ++j) Ans[i]+=1ll*W[mx][j]*P[i][j]%mod*(tmp+(mx==j?g[n>>1]:0))%mod;//cx
}
}
int main()
{
int n=read();
for(int i=1; i<=n; ++i)
P[i][0]=read(),P[i][1]=read(),P[i][2]=read(),P[i][3]=read();
for(int i=0; i<4; ++i)
W[i][0]=read(),W[i][1]=read(),W[i][2]=read(),W[i][3]=read();
for(int mx=0; mx<4; ++mx) Solve(n,mx);
for(int i=1; i<=n; ++i) printf("%d\n",(int)((Ans[i]%mod+mod)%mod));
return 0;
}
C 树(树链剖分)
是不是写出这题,树剖就无敌了啊。。
写完啦!感觉还好,也就6k嘛。
肯定要用点来表示边的颜色,然后树剖。
对于操作2,我们可以拆成:
1.将u->v路径上的点,所有连向子节点的边染成col2;
2.将u->v路径上的点,所有连向父节点的边染成col1;
3.将LCA(u,v)连向父节点的边染成col2。
那么本题的操作实际有四种:链修改、链查询、修改一条链上所有连向子节点的边、换根+子树修改。
除了链修改指向子节点的边,其它都是树剖模板(换根见BZOJ3083)。
因为是改所有指向子节点的边,所以也可以树剖维护。用to_son[x]表示x指向儿子的轻边的颜色,用另一棵线段树维护(注意只是轻边,重边要单独在第一棵树上改)。
除此之外,在第一棵线段树上维护每个点x到fa[x]的边的颜色。
这样查询时,对于重链可以直接在第一棵线段树上查;对于轻边(top[x]->fa[top[x]])的颜色,需要区分是第一棵树上top[x]的值还是第二棵树上fa[top[x]]的值。修改时再带一个时间优先级即可。
要修改的链是一样的,且to_son[x]只会单点查(切换重链时)(也是单点修改),所以只要一棵线段树同时维护两种标记就行了。
复杂度\(O(m\log^2n)\)。
另外单点查完全可以用非递归。
边界(dfn+/-1)问题要注意。(会不会还有锅啊。。)
//358ms 20428KB
#include <cstdio>
#include <cctype>
#include <algorithm>
#define fir first
#define sec second
#define mp std::make_pair
#define pr std::pair<int,int>//time val
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=1e5+5;
int n,Enum,H[N],nxt[N<<1],to[N<<1],fa[N],dep[N],sz[N],son[N],top[N],dfn[N],Index,ref[N],out[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Segment_Tree
{
#define ls rt<<1
#define rs rt<<1|1
#define lson l,m,ls
#define rson m+1,r,rs
#define S N<<2
int cnt[S][3],sz[S];
pr tag1[S],tag2[S];
#undef S
#define Update(rt) cnt[rt][0]=cnt[ls][0]+cnt[rs][0], cnt[rt][1]=cnt[ls][1]+cnt[rs][1], cnt[rt][2]=cnt[ls][2]+cnt[rs][2]
#define Cov1(rt,v) cnt[rt][0]=0, cnt[rt][1]=0, cnt[rt][2]=0, cnt[rt][v.sec]=sz[rt], tag1[rt]=v
#define Cov2(rt,v) tag2[rt]=v
inline void PushDown(int rt)
{
int l=ls,r=rs;
if(tag1[rt].fir) Cov1(l,tag1[rt]), Cov1(r,tag1[rt]), tag1[rt].fir=0;
if(tag2[rt].fir) Cov2(l,tag2[rt]), Cov2(r,tag2[rt]), tag2[rt].fir=0;
}
void Build(int l,int r,int rt)
{
cnt[rt][0]=sz[rt]=r-l+1;
if(l!=r)
{
int m=l+r>>1;
Build(lson), Build(rson);
}
}
void ModifyI(int l,int r,int rt,int L,int R,pr v)//为了常数 粘了仨Modify→_→
{
if(L<=l && r<=R) {Cov1(rt,v); return;}
PushDown(rt);
int m=l+r>>1;
if(L<=m) ModifyI(lson,L,R,v);
if(m<R) ModifyI(rson,L,R,v);
Update(rt);
}
void Modify1(int l,int r,int rt,int p,pr v)
{
if(l==r) {Cov1(rt,v); return;}
PushDown(rt);
int m=l+r>>1;
p<=m ? Modify1(lson,p,v) : Modify1(rson,p,v);
Update(rt);
}
void Modify2(int l,int r,int rt,int L,int R,pr v1,pr v2)
{
if(L<=l && r<=R) {Cov1(rt,v1), Cov2(rt,v2); return;}
PushDown(rt);
int m=l+r>>1;
if(L<=m) Modify2(lson,L,R,v1,v2);
if(m<R) Modify2(rson,L,R,v1,v2);
Update(rt);
}
pr QueryP(int l,int r,int rt,int p,int id)
{
int m;
while(l!=r)
{
PushDown(rt);
m=l+r>>1, p<=m?(r=m,rt=ls):(l=m+1,rt=rs);
}
return id==1?tag1[rt]:tag2[rt];
}
int QueryI(int l,int r,int rt,int L,int R,int c)
{
if(L<=l && r<=R) return cnt[rt][c];
PushDown(rt);
int m=l+r>>1;
if(L<=m)
if(m<R) return QueryI(lson,L,R,c)+QueryI(rson,L,R,c);
else return QueryI(lson,L,R,c);
return QueryI(rson,L,R,c);
}
}T;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
inline void AE(int u,int v)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum;
}
inline int Jump(int u,int lca)
{
int las=u;
while(top[u]!=top[lca]) u=fa[las=top[u]];
return u==lca?las:ref[dfn[lca]+1];
}
void DFS1(int x)
{
int mx=0; sz[x]=1;
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa[x])
{
fa[v]=x, dep[v]=dep[x]+1, DFS1(v), sz[x]+=sz[v];
if(sz[v]>mx) mx=sz[v], son[x]=v;
}
}
void DFS2(int x,int tp)
{
top[x]=tp, dfn[x]=++Index, ref[Index]=x;
if(son[x])
{
DFS2(son[x],tp);
for(int i=H[x],v; i; i=nxt[i])
if((v=to[i])!=fa[x]&&v!=son[x]) DFS2(v,v);
}
out[x]=Index;
}
int Query(int u,int v,int c)
{
int ans=0,tp;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) std::swap(u,v);
tp=top[u];
if(dfn[tp]<dfn[u]) ans+=T.QueryI(1,n,1,dfn[tp]+1,dfn[u],c);
pr t1=T.QueryP(1,n,1,dfn[tp],1),t2=T.QueryP(1,n,1,dfn[fa[tp]],2);
ans+=t1.fir>t2.fir?(t1.sec==c):(t2.sec==c);
u=fa[tp];
}
if(dep[u]<dep[v]) std::swap(u,v);
if(dfn[v]<dfn[u]) ans+=T.QueryI(1,n,1,dfn[v]+1,dfn[u],c);
return ans;
}
void Modify(int u,int v,int c1,int c2,int t)
{
if(dfn[u]<n) T.Modify1(1,n,1,dfn[u]+1,mp(t,c2));//dfn[son[u]]
if(dfn[v]<n) T.Modify1(1,n,1,dfn[v]+1,mp(t,c2));//这个修改就不会有冲突了
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) std::swap(u,v);
T.Modify2(1,n,1,dfn[top[u]],dfn[u],mp(t+1,c1),mp(t,c2));
u=fa[top[u]];
T.Modify1(1,n,1,dfn[u]+1,mp(t+1,c2));
}
if(dep[u]<dep[v]) std::swap(u,v);
T.Modify2(1,n,1,dfn[v],dfn[u],mp(t+1,c1),mp(t,c2));
T.Modify1(1,n,1,dfn[v],mp(t+1,c2));
}
int main()
{
n=read();
for(int i=1; i<n; ++i) AE(read(),read());
DFS1(1), DFS2(1,1), T.Build(1,n,1);
for(int opt,x,y,c,c2,rt,Q=read(),i=1; i<=Q; ++i)
switch(opt=read())
{
case 1: x=read(),y=read(),printf("%d\n",Query(x,y,read())); break;
case 2: x=read(),y=read(),c=read(),c2=read(),Modify(x,y,c,c2,i<<1); break;
case 3:
{
rt=read(),x=read(),c=read();
if(x==rt) T.ModifyI(1,n,1,1,n,mp(i<<1,c));
else if(dfn[x]<=dfn[rt]&&dfn[rt]<=out[x])
{
y=Jump(rt,x), T.ModifyI(1,n,1,1,dfn[y]-1,mp(i<<1,c));
if(out[y]<n) T.ModifyI(1,n,1,out[y]+1,n,mp(i<<1,c));
}
else if(dfn[x]<out[x]) T.ModifyI(1,n,1,dfn[x]+1,out[x],mp(i<<1,c));
break;
}
}
return 0;
}
考试代码
A
傻了,一直不会统计答案,只要把两个关键字的顺序一换就行了啊QAQ。
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define mp std::make_pair
#define pr std::pair<int,int>
#define gc() getchar()
#define MAXIN 300000
//#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define mod 998244353
#define Mod(x) x>=mod&&(x-=mod)
typedef long long LL;
const int N=1e6+5;
int n,m,outd[N],ind[N],pw[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Graph
{
int Enum,H[N],nxt[N],to[N],len[N];
void AE(int u,int v,int w)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
}
}G,R;
struct Node
{
int id;
pr x;
inline bool operator <(const Node &a)const
{
return x<=a.x;
}
}A[N];
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
void Toposort()
{
static int q[N],f[N],lev[N],lev2[N],pre[N],sum[N],dgr[N];
static bool inq[N],vis[N];
int h=0,t=0;
memcpy(dgr,outd,sizeof dgr);
for(int i=1; i<=n; ++i) if(!dgr[i]) q[++t]=i;
while(h<t)
{
int x=q[++h]; inq[x]=1;
for(int i=R.H[x],v; i; i=R.nxt[i])
{
f[v=R.to[i]]=std::max(f[v],f[x]+1);
if(!--dgr[v]) q[++t]=v;
}
}
memset(lev,0x3f,sizeof lev);
memcpy(dgr,ind,sizeof dgr);
h=0,t=0;
for(int i=1; i<=n; ++i) if(!dgr[i]) q[++t]=i,lev[i]=1,lev2[i]=1;
int Gap=t;
while(h<t)
{
if(h==Gap)
{
for(int i=h+1; i<=t; ++i) A[i]=(Node){q[i],mp(lev[q[i]],pre[q[i]])}, lev2[q[i]]=lev[q[i]];
std::sort(A+h+1,A+t+1);
int tmp=1; lev[A[h+1].id]=1;
for(int i=h+2; i<=t; ++i)
if(A[i].x!=A[i-1].x) lev[A[i].id]=++tmp;
else lev[A[i].id]=tmp;
// for(int i=h+1; i<=t; ++i)
// printf("%d:id:%d lev:%d pre:%d\n",i,A[i].id,lev[A[i].id],pre[A[i].id]); puts("");
Gap=t;
}
int x=q[++h];
for(int i=G.H[x],v; i; i=G.nxt[i])
{
if(f[v=G.to[i]]+1==f[x])
{
if(lev[v]>lev[x])
lev[v]=lev[x], pre[v]=G.len[i];//, sum[v]=sum[x]*29%mod+pre[v], Mod(sum[v]);
else if(lev[v]==lev[x]&&pre[v]>G.len[i])
pre[v]=G.len[i];//, sum[v]=sum[x]*29%mod+pre[v], Mod(sum[v]);
// printf("v:%d lev[v]=%d pre[v]=%d pw[%d]=%d\n",v,lev[v],pre[v],f[v],pw[f[v]]);
}
if(!--dgr[v]) q[++t]=v;
}
}
// puts("");
// for(int i=1; i<=n; ++i) printf("%d:lev:%d lev2:%d pre:%d\n",i,lev[i],lev2[i],pre[i]); puts("");
memcpy(dgr,outd,sizeof dgr);
h=0,t=0;
for(int i=1; i<=n; ++i) if(!dgr[i]) q[++t]=i;
while(h<t)
{
int x=q[++h];
for(int i=R.H[x],v; i; i=R.nxt[i])
{
if(f[v=R.to[i]]==f[x]+1 && lev2[v]==lev2[x] && pre[x]==R.len[i])
sum[v]=sum[x]*29%mod+pre[x], Mod(sum[v]);
if(!--dgr[v]) q[++t]=v;
}
}
for(int i=1; i<=n; ++i)
if(inq[i]) printf("%d\n",(int)(1ll*sum[i]*29%mod));
else puts("Infinity");
}
int main()
{
// freopen("A1.in","r",stdin);
// freopen(".out","w",stdout);
n=read(),m=read();
pw[0]=1;
for(int i=1; i<=n; ++i) pw[i]=1ll*pw[i-1]*29%mod;
for(int i=1,u,v,w; i<=m; ++i) u=read(),v=read(),w=read(),++outd[u],++ind[v],G.AE(u,v,w),R.AE(v,u,w);
Toposort();
return 0;
}
很久以前的奇怪但现在依旧成立的签名
attack is our red sun $$\color{red}{\boxed{\color{red}{attack\ is\ our\ red\ sun}}}$$ ------------------------------------------------------------------------------------------------------------------------