題目難度相較前些年會相對簡單一點?(FAKE.jpgnode
平均碼量符合WC風格?(甚至更多一點ios
出題人良心!dom
顯然我是不可能寫的,這輩子都不可能寫的。優化
那麼顯然,咱們仍是要分析性質的!ui
咱們想到把第一個樹的點權第二個樹的點權附到第三棵樹上,而後作直徑就完事了。spa
對於邊權非負,貪心解決樹的直徑是對的,全部連接兩個樹以後的直徑顯然就是原先的$4$個直徑的斷點的$C(4,2)$的組合最大的那個。code
而後顯然,第一棵樹的點權即爲$dis_x-dis_{now}$,第二顆樹的點權即爲:$dis_x$,這樣,附到第三棵樹上求直徑便可。get
而後首先至關因而一開始只有一坨的點,而後在第二顆樹的虛樹上合併便可,那麼合併的過程當中就能夠知道在第二顆樹的$LCA$了,更新答案的時候減去便可。string
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> using namespace std; #define ll long long #define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++) char buf[100000],*p1,*p2; inline int rd() { register int x=0;register char c=nc(); while(c<48)c=nc();while(c>47)x=((x+(x<<2))<<1)+(c^48),c=nc(); return x; } inline ll Rd() { register ll x=0;register char c=nc(); while(c<48)c=nc();while(c>47)x=((x+(x<<2))<<1)+(c^48),c=nc(); return x; } int n,size,q[100005],col[100005],lg[200005];ll val[100005],ans; namespace T3 { const int N = 100005; struct node{int to,next;ll val;}e[N<<1]; int head[N],cnt,idx[N],tims;ll mn[18][N<<1],dis[N]; inline void add(int x,int y,ll z){e[cnt]=(node){y,head[x],z};head[x]=cnt++;} void dfs(int x,int from) { mn[0][++tims]=dis[x];idx[x]=tims; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from)dis[to1]=dis[x]+e[i].val,dfs(to1,x),mn[0][++tims]=dis[x]; } } void init() { memset(head,-1,sizeof(head));ll z; for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),z=Rd(),add(x,y,z),add(y,x,z); dfs(1,0);for(int i=2;i<=tims;i++)lg[i]=lg[i>>1]+1; for(int i=1;i<=17;i++) for(int j=1;j+(1<<i)-1<=tims;j++) mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]); } inline ll get_lca(int x,int y){if(x==y)return 0ll;if(x>y)swap(x,y);int tmp=lg[y-x+1];return min(mn[tmp][x],mn[tmp][y-(1<<tmp)+1]);} inline ll get_dis(int x,int y){if(x==y)return 0ll;return dis[x]+dis[y]-(get_lca(idx[x],idx[y])<<1);} } namespace T2 { const int N = 100005; struct node{int to,next;ll val;}e[N<<1]; int head[N],cnt,mn[18][N<<1],idx[N],tims,dep[N];ll dis[N]; inline void add(int x,int y,ll z){e[cnt]=(node){y,head[x],z};head[x]=cnt++;} inline void add(int x,int y){e[cnt]=(node){y,head[x],0ll};head[x]=cnt++;} inline int cmp1(int x,int y){return dep[x]<dep[y]?x:y;} inline bool cmp(int x,int y){return idx[x]<idx[y];} void dfs(int x,int from) { mn[0][++tims]=x;idx[x]=tims;dep[x]=dep[from]+1; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from)dis[to1]=dis[x]+e[i].val,dfs(to1,x),mn[0][++tims]=x; } } inline int get_lca(int x,int y){if(x==y)return x;x=idx[x],y=idx[y];if(x>y)swap(x,y);int tmp=lg[y-x+1];return cmp1(mn[tmp][x],mn[tmp][y-(1<<tmp)+1]);} inline ll get_dis(int x,int y){return dis[x]+dis[y]-(dis[get_lca(x,y)]<<1);} void init() { memset(head,-1,sizeof(head));memset(col,-1,sizeof(col));ll z; for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),z=Rd(),add(x,y,z),add(y,x,z); dfs(1,0); for(int i=1;i<=17;i++) for(int j=1;j+(1<<i)-1<=tims;j++) mn[i][j]=cmp1(mn[i-1][j],mn[i-1][j+(1<<i-1)]); memset(head,-1,sizeof(head));cnt=0;tims=0; } struct DP { int x,y;ll len; DP(){x=y=0;len=-1ll;} DP(int a,int b){x=a,y=b,len=T3::get_dis(a,b)+val[a]+val[b]+dis[a]+dis[b];} DP(int a,int b,ll c){x=a,y=b,len=c;} inline bool operator < (const DP &a) const {return len<a.len;} inline friend DP operator + (const DP &a,const DP &b) { if(!a.x)return b;if(!b.x)return a; return max(max(a,b),max(max(DP(a.x,b.x),DP(a.y,b.x)),max(DP(a.x,b.y),DP(a.y,b.y)))); } inline friend DP operator * (const DP &a,const DP &b) { if(!a.x)return DP();if(!b.x)return DP(); return max(max(DP(a.x,b.x),DP(a.y,b.x)),max(DP(a.x,b.y),DP(a.y,b.y))); } }f[N][2]; int sta[N],top; void dfs_dp(int x) { f[x][0]=f[x][1]=DP();if(col[x]!=-1)f[x][col[x]]=DP(x,x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to;dfs_dp(to1); for(int j=0;j<2;j++)ans=max(ans,(f[x][j]*f[to1][!j]).len-(dis[x]<<1)); for(int j=0;j<2;j++)f[x][j]=f[x][j]+f[to1][j]; }head[x]=-1; // printf("%d %d %d %lld %d %d %lld\n",x,f[x][0].x,f[x][0].y,f[x][0].len,f[x][1].x,f[x][1].y,f[x][1].len); } void build() { sort(q+1,q+size+1,cmp);cnt=top=0;sta[++top]=1; for(int i=1;i<=size;i++) { int x=q[i],lca=get_lca(x,sta[top]); while(top&&dep[lca]<=dep[sta[top-1]])add(sta[top-1],sta[top]),top--; if(sta[top]!=lca)add(lca,sta[top]),sta[top]=lca;if(sta[top]!=x)sta[++top]=x; }while(top>1)add(sta[top-1],sta[top]),top--;dfs_dp(1); for(int i=1;i<=size;i++)col[q[i]]=-1,val[q[i]]=0ll; } } namespace T1 { const int N = 200005; struct node{int to,next;ll val;}e[N<<1]; int head[N],cnt,tot;vector<int>v[N];vector<ll>va[N]; inline void add_edge(int x,int y,ll z){v[x].push_back(y),va[x].push_back(z);} inline void add(int x,int y,ll z){e[cnt]=(node){y,head[x],z};head[x]=cnt++;} int vis[N],siz[N],mx,rot,sn;//0 fa 1 ls 2 rs void dfs_change(int x,int from) { int tmp=0,lst=x,lim=v[x].size(); for(int i=0;i<lim;i++) { int to1=v[x][i]; if(to1!=from) { if(tmp&&(tmp!=lim-(x==1)))++tot,add(lst,tot,0ll),add(tot,lst,0ll),lst=tot; tmp++,add(lst,to1,va[x][i]),add(to1,lst,va[x][i]); dfs_change(to1,x); } } } void get_root(int x,int from) { siz[x]=1; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[i>>1]) { get_root(to1,x);siz[x]+=siz[to1]; if(max(siz[to1],sn-siz[to1])<mx)rot=i,mx=max(siz[to1],sn-siz[to1]); } } } int flg=0; void calc(int x,int from,ll dis) { if(x<=n)q[++size]=x,col[x]=flg,val[x]=dis;siz[x]=1; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[i>>1])calc(to1,x,dis+e[i].val),siz[x]+=siz[to1]; } } void dfs(int x) { mx=1<<30;sn=siz[x];rot=0;get_root(x,0); if(!rot)return ;vis[rot>>1]=1;//printf("%d %d\n",e[rot].to,e[rot^1].to); size=0;flg=0;calc(e[rot].to,0,0);flg=1;calc(e[rot^1].to,0,e[rot].val); T2::build();int tmp=e[rot^1].to;dfs(e[rot].to);dfs(tmp); } void init() { memset(head,-1,sizeof(head));tot=n;ll z; for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),z=Rd(),add_edge(x,y,z),add_edge(y,x,z); T2::init();T3::init();dfs_change(1,0);siz[1]=tot;dfs(1); } } int main(){scanf("%d",&n);T1::init();printf("%lld\n",ans);}
其實只要會如何判斷是否有歐拉回路便可拿到$44$分it
顯然,DP方程:$f[S]=\sum\limits_{s\sub S}f[s]\times g[S - s]$,其中$f[s]$表示$s$這些點構成的點集的方案數,$g[s]$表示$[check (s)]\sum\limits_{i\in s}w_i$
這個東西跑$3^{21}$即便是$10s$開$O2$,也是不太現實的
那麼就須要用到FWT的高級操做了...
顯然,能夠把枚舉子集看作兩個集合的交集爲這個集合,也就是集合取或。
可是這樣顯然會有重複的狀況,那麼就須要用一點別的思想:設$f[i][S]$表示用$i$個$1$組成$S$的方案數。
顯然,第一感受這個東西沒啥用處,可是能夠這樣:$f[i][S]=[x|y==S]\sum\limits_{j=1}^i f[j][x]\times g[i-j][y]$
能夠看出,這樣$x,y$之間就不存在交集,也就是說,將重複的狀況徹底去掉了。
代碼.jpg
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> using namespace std; #define N 22 #define M 1<<21 #define ll long long #define mod 998244353 int w[N],n,m,p,map[N][N],d[N],q[N],vis[N],tims; int f[N][M],g[N][M],cnt[M],s[M]; int q_pow(int x,int n){int ret=1;for(;n;n>>=1,x=(ll)x*x%mod)if(n&1)ret=(ll)ret*x%mod;return ret;} bool check(int S) { memset(d,0,sizeof(d)); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(map[i][j]&&((1<<i-1)&S)&&((1<<j-1)&S))d[i]++; int l=0,r=0;tims++; for(int i=1;i<=n;i++)if(((1<<i-1)&S)&&(d[i]&1))return 0; for(int i=1;i<=n;i++)if((1<<i-1)&S){q[r++]=i;vis[i]=tims;break;} while(l<r) { int x=q[l++]; for(int i=1;i<=n;i++)if(vis[i]!=tims&&map[x][i]&&((1<<i-1)&S))vis[i]=tims,q[r++]=i; } return r==cnt[S]; } void FWT(int *a,int len,int opt) { for(int k=2;k<=len;k<<=1) for(int i=0,t=k>>1;i<len;i+=k) for(int j=i;j<i+t;j++) if(opt==1)a[j+t]=(a[j+t]+a[j])%mod; else a[j+t]=(a[j+t]-a[j]+mod)%mod; } int main() { scanf("%d%d%d",&n,&m,&p); for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),map[x][y]=map[y][x]=1; for(int i=1;i<=n;i++)scanf("%d",&w[i]); f[0][0]=1; int len=1<<n; for(int S=1;S<len;S++) { cnt[S]=cnt[S>>1]+(S&1); for(int i=1;i<=n;i++)if(S&(1<<i-1))s[S]+=w[i]; if(p==0)s[S]=1; else if(p==2)s[S]=(ll)s[S]*s[S]%mod; if(!check(S))g[cnt[S]][S]=s[S]; s[S]=q_pow(s[S],mod-2);f[0][S]=1; } for(int i=1;i<=n;i++)FWT(g[i],len,1); for(int i=1;i<=n;i++) { for(int j=1;j<=i;j++) for(int S=0;S<len;S++) f[i][S]=(f[i][S]+(ll)f[i-j][S]*g[j][S])%mod; FWT(f[i],len,-1); for(int S=0;S<len;S++) if(cnt[S]!=i)f[i][S]=0; else f[i][S]=(ll)f[i][S]*s[S]%mod; if(i!=n)FWT(f[i],len,1); } printf("%d\n",f[n][len-1]); }
講道理,我以爲難點在鏈上...
能夠看出,他的詢問次數基本在$n\log n$級別,(除了鏈.jpg
那麼考慮暴力如何處理:顯然是每次隨機找到一個點,而後向下拓展便可。
這樣的詢問複雜度爲$n^2$級別的(我以爲高級一點的隨機化能拿很高分.jpg
而後顯然你發現,這個東西的確切詢問次數爲:$\sum\limits_{i=1}^ndep_i$
這個東西,你想優化就很簡單了!
考慮,每次只會查詢$\log n$次,那麼能夠用點分樹或者LCT(隨機剖分.jpg
每次最多跳$\log n$層,由於每次跳的時候,要麼跳一整條鏈,要麼在$Splay$上跳$\log n$次。
那麼就能夠找到未遍歷到的節點啦!
找到後暴力拓展的複雜度顯然正確.jpg
那麼對於鏈來講,相對麻煩一些,每次隨機的找一個未遍歷的點,分別對左右兩端拓展,若是往左沒被遍歷就往左,不然必定在右側。
對於這樣的東西,他的指望錯誤次數在$\log n$之內,多隨機打亂兩次就行了嘛...(點黑誰也不能怪.jpg
代碼!
#include "rts.h" #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> using namespace std; #define N 300005 #define ls ch[rt][0] #define rs ch[rt][1] #define get(rt) (ch[f[rt]][0]!=rt) #define isroot(rt) (ch[f[rt]][0]!=rt&&ch[f[rt]][1]!=rt) int ch[N][2],f[N],vis[N],idx[N],mn[N],mx[N]; inline void PushUp(int rt) { mn[rt]=mx[rt]=rt; if(ls)mn[rt]=mn[ls]; if(rs)mx[rt]=mx[rs]; } inline void rotate(int rt) { int x=f[rt],y=f[x],k=get(rt); if(!isroot(x))ch[y][ch[y][0]!=x]=rt; ch[x][k]=ch[rt][!k];f[ch[x][k]]=x; ch[rt][!k]=x;f[x]=rt;f[rt]=y;PushUp(x);PushUp(rt); } inline void Splay(int rt) { for(;!isroot(rt);rotate(rt)) if(!isroot(f[rt]))rotate(get(f[rt])==get(rt)?f[rt]:rt); } inline void access(int rt){int t=0;while(rt)Splay(rt),rs=t,PushUp(rt),t=rt,rt=f[rt];} inline int get_root(int rt){while(!isroot(rt))rt=f[rt];return rt;} void play(int n,int T,int dataType) { for(int i=2;i<=n;i++)idx[i]=i; random_shuffle(idx+2,idx+n+1); if(dataType==3) { vis[1]=1;int l=1,r=1; for(int i=2;i<=n;i++) { int x=idx[i],now;if(vis[x])continue; if(!vis[now=explore(l,x)]) { while(now!=x)vis[now]=1,now=explore(now,x); vis[x]=1;l=x; }else { now=explore(r,x); while(now!=x)vis[now]=1,now=explore(now,x); vis[x]=1;r=x; } } return; } else { vis[1]=1;mn[1]=mx[1]=1; for(int i=2;i<=n;i++) { if(vis[idx[i]])continue; int x=idx[i],rt=get_root(1),ret; while(!vis[x]) { ret=explore(rt,x); if(mn[rs]==ret)rt=rs; else if(mx[ls]==ret)rt=ls; else if(vis[ret])rt=get_root(ret); else vis[ret]=1,mn[ret]=mx[ret]=ret,f[ret]=rt,rt=ret; } access(x); } }return; }