3.28省选模拟
Future can be expectation and never give up in any case
I never agree to give up this sentence
重新玩了一下,手感还在
我太菜了,又垫底了
\(T1\)感知了一下是个神仙的\(dp,\)决策的时候还无法直接转移,就写了个暴力\(dp\ kill\)
\(T2\)想从图论上搞事情,然后我连周期都不会...\(graph\ theory\ kill\)
\(T3\)发现菊花和第一个部分很好拿分,链的部分并没有想到,尽管考完后被告知链最好写了.\(graph\ theory\ double \ kill\)
\(T1\)分身游走
神仙\(dp\)实锤了
首先比较好想的是\(k=1,\)除了最长链都被走了两次
若\(k=2,\)如果一开始有两个分叉,然后能节省一个直径的距离
然后推导性质\(:\)
\(1.\)在最优方案中,如果进入一个子树大于两个,那么不可能都回来,如果都回来,和一个进去是一样的,显然没必要
\(2.\)如果至少两个分身进入以\(x\)为根的子树,那么这些分身都应该停在以\(x\)为根的子树内,否则的话,要么一个进去,否则都不出来
我们现在可以把\(dp\)优化到\(O(n^2k^2)\)得到\(50pts\)
那开始终极的\(dp\)
我们的时间复杂度主要是,枚举根每次需要重新跑一遍树,有很多\(dp\)转移时等价的
我们转移\(dp\)可以枚举一条边\((x,y),\)计算以\(y\)为根,\(x\)子树内部的\(dp,\)以后再使用的时候直接调取就好了
随机数据下远远达不到\(O(n^2k^2),\)惊叹于出题人的思路,就是预处理\(dp,\)以后不用转移了
至于如果怕被卡的话,拆点可以更加稳定
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MAXN 30005
//反正我们求的是每个根的值
//拆点之后算的也是对的
using namespace std;
int head[MAXN],nxt[MAXN],val[MAXN],to[MAXN],id[MAXN],Num,tot=1,k;
int dp[MAXN][35],Ans[35],n;
vector<pair<int,int> >rd[MAXN];
bool vis[MAXN];
void add(int u,int v,int w)
{
tot++;
to[tot]=v;
val[tot]=w;
nxt[tot]=head[u];
head[u]=tot;
tot++;
to[tot]=u;
val[tot]=w;
nxt[tot]=head[v];
head[v]=tot;
}
void dfs_cd(int now)
{
id[now]=++Num;
for(int x=id[now],i=0;i<rd[now].size();i++)
{
int y=rd[now][i].first;
if(!id[y])
{
++Num;
add(x,Num,0);
x=Num;
add(x,Num+1,rd[now][i].second);
dfs_cd(y);
}
}
}
void sol(int now,int fa)
{
if(dp[fa][0]!=INF) return ;
dp[fa][0]=0;
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(i==(fa^1)) continue;
sol(y,i);
for(int j=k;j>=1;j--)
{
//枚举选多少个,倒序背包
int tmp=INF;
//我们这个过程是对边dp的过程
//dp[now][i]表示的是以now这条边连接的两点
//fa->x,x子树留下i个的最大值
//第一个转移,是一个不留,就是全部上来
for(int k=0;k<=j;k++)
{
tmp=min(tmp,dp[fa][j-k]+dp[i][k]+val[i]*(k==0?2:k));
}
//显然我们这个每一步转移都保证了边的添加
dp[fa][j]=tmp;
}
dp[fa][0]+=dp[i][0]+val[i]*2;
}
for(int i=1;i<=k;i++)
{
dp[fa][i]=min(dp[fa][i],dp[fa][i-1]);
}
}
int main()
{
freopen("move.in","r",stdin);
freopen("move.out","w",stdout);
scanf("%d%d",&n,&k);
memset(dp,0x3f,sizeof(dp));
for(int i=1,u,v,w;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
rd[u].push_back(make_pair(v,w));
rd[v].push_back(make_pair(u,w));
}
dfs_cd(1);
for(int i=2;i<=tot;i++)
{
sol(to[i],i);
}
for(int rt=1;rt<=n;rt++)
{
int x=id[rt];
Ans[0]=0;
for(int i=head[x];i;i=nxt[i])
{
for(int l=k;l>=1;l--)
{
Ans[l]+=dp[i][0]+val[i]*2;
//Ans表示前若干个子树留下l的答案
//首先我们必然先进去,然后再转移
for(int j=1;j<=l;j++)
{
Ans[l]=min(Ans[l],Ans[l-j]+dp[i][j]+val[i]*j);
}
}
Ans[0]+=dp[i][0]+val[i]*2;
}
for(int i=1;i<=k;i++)
{
Ans[i]=min(Ans[i],Ans[i-1]);
}
cout<<Ans[k]<<endl;
}
}
\(T2\)迷梦深层
这个题有一个正规的名字,有向图邻接矩阵的幂敛指数与周期(听上去就很强)
我们设幂敛质数为\(k\),也就是我们这个矩阵开始出现循环的位置
设周期为\(d,\)开始循环之后\(d\)为一个周期
首先考虑\(d,\)一个强联通分量的周期是所有环长度的最大公约数
较为显然,我们可以用环拼出所有长度为最大公约数倍数的循环
然后一个图的周期是所有强联通分量的周期的最小公倍数
结论\(k\)上界是\(O(n^2)\)
首先求\(d,\)我们考虑怎么找到每个联通分量里的环,
我们对于这个强连通分量\(dfs,\)当我们找到一个返祖边,长度差/环长为\(d=i-j+1\)
我们知道\(gcd(a,b)=gcd(b,a-b)\)直接\(gcd\)即可
那么还有一个\(k,\)我们有了周期,如果在大于\(k,\)那么我们往后走\(d\)就一样,否则不一样,然后简单计算一下就好了
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
#define MAXN 500005
using namespace std;
int head[MAXN],nxt[MAXN],dep[MAXN],to[MAXN],cut,tot,n,m,t;
int Sum[MAXN],col[MAXN],dfn[MAXN],low[MAXN],st[MAXN],tim,top,G,L=1;
bool Lai[MAXN],vis[MAXN],ins[MAXN];
vector<int>Fin;
set<int>zong;
struct node
{
int jz[205][205];
void Init()
{
memset(jz,0,sizeof(jz));
}
}St;
void add(int u,int v)
{
tot++;
to[tot]=v;
nxt[tot]=head[u];
head[u]=tot;
}
void tarjan(int now)
{
dfn[now]=low[now]=++tim;
ins[now]=true;
st[++top]=now;
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(!dfn[y])
{
tarjan(y);
low[now]=min(low[now],low[y]);
}
else if(ins[y])
{
low[now]=min(low[now],dfn[y]);
}
}
if(low[now]==dfn[now])
{
int z;
cut++;
do
{
z=st[top--];
ins[z]=false;
col[z]=cut;
}while(z!=now);
}
}
int gcd(int a,int b)
{
if(a%b==0) return b;
return gcd(b,a%b);
}
int lcm(int a,int b)
{
return a/gcd(a,b)*b;
}
void dfs(int now,int now_col)
{
Lai[now]=true;
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(col[y]!=now_col) continue;
if(Lai[y])
{
if(!G) G=dep[now]-dep[y]+1;
else if(dep[now]-dep[y]+1!=0)G=gcd(G,abs(dep[now]-dep[y]+1));
continue;
}
dep[y]=dep[now]+1;
dfs(y,now_col);
}
}
int my_pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
{
res=(res*a)%mod;
}
a=(a*a)%mod;
b>>=1;
}
return res;
}
node mul(node a,node b)
{
node res;
res.Init();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
res.jz[i][j]|=a.jz[i][k]&b.jz[k][j];
}
}
}
return res;
}
node my_jzpow(node a,int b)
{
node res=a;
b--;
while(b)
{
if(b&1)
{
res=mul(res,a);
}
a=mul(a,a);
b>>=1;
}
return res;
}
bool check(int mid)
{
node res=my_jzpow(St,mid);
node res1=my_jzpow(St,mid+L);
int flag=true;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(res.jz[i][j]!=res1.jz[i][j])
{
flag=false;
goto EB;
}
}
}
EB:;
return flag;
}
signed main()
{
freopen("lost.in","r",stdin);
freopen("lost.out","w",stdout);
scanf("%lld%lld%lld",&n,&m,&t);
for(int i=1,u,v;i<=m;i++)
{
scanf("%lld%lld",&u,&v);
add(u,v);
if(t)St.jz[u][v]=1;
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++)
{
if(!vis[col[i]])
{
vis[col[i]]=true;
G=0;
dfs(i,col[i]);
if(!G) continue;
Fin.push_back(G);
}
}
for(int i=0;i<Fin.size();i++)
{
int now=Fin[i];
for(int j=2;j*j<=now;j++)
{
int res=0;
while(now%j==0)
{
res++;
now/=j;
}
zong.insert(j);
Sum[j]=max(Sum[j],res);
}
if(now) zong.insert(now);
Sum[now]=max(Sum[now],1ll);
}
L=1;
for(set<int>::iterator it=zong.begin();it!=zong.end();it++)
{
(L*=my_pow(*it,Sum[*it]))%=mod;
}
if(t==0)cout<<L<<" ",exit(0);
int l=1,r=n*n,mid,ans;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid))
{
r=mid-1;
ans=mid;
}
else
{
l=mid+1;
}
}
cout<<ans<<" "<<L;
}
由于这个代码没有使用矩阵"块"速幂,只能拿\(70pts\)
下面给个矩阵块速幂板子
matrix operator * (matrix a , matrix b) {
memset(tmp.a , 0 , sizeof(tmp.a));
memset(matsta , 0 , sizeof(matsta));
for (int i = 1; i <= n; i++){
for (int j = 1 , k= 1 , l = 0; j <= n ; j++ , l++) {
if (a[i][j]) matsta[0][i][k] |= 1 << l;
if (j % 20 == 0) k++ , l=0;
}
}
for (int i = 1; i <= n; i++){
for (int j = 1 , k = 1 , l = 0; j <= n; j++ , l++) {
if (b[j][i]) matsta[1][i][k] |= 1 << l;
if (j % 20 == 0) k++ , l = 0;
}
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
for (int k = 1; k <= K; k++){
if (matsta[0][i][k] & matsta[1][j][k]) {
tmp[i][j] = 1;
break;
}
}
}
}
return tmp;
}
\(T3\)树上游戏
部分分拿满可以得到\(70pts,\)大家眼中的难度链\(<\)菊花,我眼中的难度菊花\(<\)链
第一个\(sub,O(n\times m)\)暴力不用多说,
第二个\(sub,\)菊花部分,将询问排序,先找大的,全扫一遍的不超过两个,随机下每次指针移动两次得到结果,均摊下来是\(O(m)\)
第三个\(sub,\)链部分,直接三个\(set\)分别维护,取最大就好了
正解的话,考虑一条路径的所有贡献情况
\(val_1:\)端点对子树内部做贡献
\(val_2:LCA\)对子树外做贡献
\(val_3:LCA\)的其他儿子按照\(val_1\)的方法统计贡献
\(val4:\)对于链上其他点,会向自己子树按照\(val_1\)的计算方式做贡献
实现方法\(:\)把路径拆成两条链,链上所有点去\(\max,\)其余点\(w_x\)表示所有经过此点祖先的路径贡献权值的最大值,进入一个儿子,用其他儿子最大值去更新就好了,需要亿些数据结构去维护
首先附上\(50pts\)暴力代码
#include<bits/stdc++.h>
#define MAXN 210000
using namespace std;
int head[MAXN],nxt[MAXN],val[MAXN],to[MAXN],f[MAXN],tot,n,m;
int son[MAXN],dis[MAXN],dep[MAXN],siz[MAXN],top[MAXN],lu[MAXN];
struct node
{
int x,y,dis,lca;
}rd[MAXN];
void add(int u,int v,int w)
{
tot++;
to[tot]=v;
val[tot]=w;
nxt[tot]=head[u];
head[u]=tot;
}
void dfs_pre(int now,int fa)
{
int maxn=-1;
dep[now]=dep[fa]+1;
siz[now]=1;
f[now]=fa;
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(y==fa) continue;
dis[y]=dis[now]+val[i];
dfs_pre(y,now);
siz[now]+=siz[y];
if(siz[y]>maxn)
{
maxn=siz[y];
son[now]=y;
}
}
}
void dfs_top(int now,int topn)
{
top[now]=topn;
if(!son[now]) return ;
dfs_top(son[now],topn);
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(top[y]) continue;
dfs_top(y,y);
}
}
int LCA(int u,int v)
{
// cout<<"LCA: "<<u<<" "<<v<<"\n";
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=f[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
return u;
}
int LEN(int x,int y)
{
return dis[x]+dis[y]-2*dis[LCA(x,y)];
}
bool IN(int k,int x,int y)
{
return LEN(x,y)==LEN(k,x)+LEN(k,y);
}
bool cmp1(node a,node b)
{
return a.dis>b.dis;
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&n,&m);
if(n<=2000)
{
for(int i=1,u,v,w;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
dfs_pre(1,1);
dfs_top(1,1);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&rd[i].x,&rd[i].y);
rd[i].lca=LCA(rd[i].x,rd[i].y);
}
for(int i=1,res;i<=n;i++)
{
res=0;
for(int j=1;j<=m;j++)
{
if(IN(i,rd[j].x,rd[j].y))
{
res=max(res,LEN(rd[j].x,rd[j].y));
}
else
{
res=max(res,LEN(rd[j].x,i)+LEN(rd[j].y,i));
}
}
printf("%d ",res);
}
}
else
{
for(int i=1,u,v,w;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
lu[max(u,v)]=w;
}
for(int i=1,u,v;i<=m;i++)
{
scanf("%d%d",&u,&v);
if(u>v) swap(u,v);
rd[i].x=u,rd[i].y=v;
if(min(u,v)==1&&max(u,v)==1)
{
rd[i].dis=0;
}
else if(min(u,v)==1)
{
rd[i].dis=lu[max(u,v)];
}
else
{
rd[i].dis=lu[u]+lu[v];
}
}
sort(rd+1,rd+1+m,cmp1);
int res=0;
for(int i=1,poi1=0,poi2=0;i<=m;i++)
{
if(rd[i].x==1)
{
res=max(res,rd[i].dis);
}
else
{
if(!poi1)
{
poi1=i;
continue;
}
else
{
res=max(res,rd[poi1].dis+rd[poi2].dis);
break;
}
}
}
printf("%d ",res);
for(int i=2;i<=n;i++)
{
bool flag=false;
for(int j=1;j<=m;j++)
{
if(rd[j].x!=i&&rd[j].y!=i)
{
printf("%d ",rd[j].dis+2*lu[i]);
flag=true;
break;
}
}
if(!flag)
{
printf("%d ",rd[1].dis);
}
}
}
}
以下是正解代码
#include<bits/stdc++.h>
#define INF 1000000000
#define MAXN 200010
using namespace std;
int rd(){
int r = 0 , w = 1;
char ch = getchar();
while(ch > '9' || ch < '0') {if(ch == '-') w = -1; ch = getchar();}
while(ch >='0' && ch <='9') {r = r * 10 + ch -'0'; ch = getchar();}
return r * w;
}
int n,m,Idx,cnt,tmp;
int head[MAXN],dfn[MAXN],top[MAXN],dep[MAXN],dis[MAXN],id[MAXN],fval[MAXN],siz[MAXN],son[MAXN],fa[MAXN],w1[MAXN];
struct EDGE
{
int to,nxt,w;
};
EDGE *ed[MAXN<<1];
void add(int x,int y,int z)
{
ed[++cnt]=new EDGE;
ed[cnt]->to=y;
ed[cnt]->nxt=head[x];
ed[cnt]->w=z;
head[x]=cnt;
}
void dfs1(int x,int f)
{
fa[x]=f;
dep[x]=dep[f]+1;
siz[x]=1;
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
int w=ed[i]->w;
if(to==f) continue;
dis[to]=dis[x]+w;
fval[to]=w;
dfs1(to,x);
siz[x]+=siz[to];
if(siz[to]>siz[son[x]]) son[x]=to;
}
}
void dfs2(int x,int tp)
{
top[x]=tp;
id[dfn[x]=++Idx]=x;
if(son[x]) dfs2(son[x] ,tp);
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
if(to==tp||to==fa[x]||to==son[x]) continue;
dfs2(to,to);
}
}
namespace Segment_Tree
{
#define ls (x<<1)
#define rs ((x<<1)|1)
struct node
{
int l,r,Max;
};
node tr[MAXN<<2];
void build(int x,int l,int r)
{
tr[x].l=l,tr[x].r=r;
tr[x].Max=-1;
if(l==r) return;
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
void update(int x,int l,int r,int k)
{
if(l>r) return;
if(l<=tr[x].l&&tr[x].r<=r)
{
tr[x].Max=max(tr[x].Max,k);
return;
}
int mid=tr[x].l+tr[x].r>>1;
if(l<=mid) update(ls,l,r,k);
if(r>mid) update(rs,l,r,k);
}
int Getmax(int x,int pos,int now=-INF)
{
now=max(now,tr[x].Max);
if(tr[x].l==tr[x].r) return now;
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) return Getmax(ls,pos,now);
else return Getmax(rs,pos,now);
}
}
struct qr
{
int x,y,w;
friend bool operator < (qr x , qr y)
{
return x.w > y.w;
}
};
qr P[MAXN << 1];
int pcnt;
int lca(int x , int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int Fa[MAXN];
int Find(int x)
{
return Fa[x]==x?x:Fa[x]=Find(Fa[x]);
}
bool check(int x,int y)
{
if(dfn[y]<=dfn[x]&&dfn[x]<=dfn[y]+siz[y]-1) return 1;
return 0;
}
void update(int x,int y,int k)
{
x=Find(x);
while(dep[x]>dep[y]) w1[x]=k,Fa[x]=fa[x],x=Find(x);
}
queue<int>q;
bool vis[MAXN];
int bfn[MAXN];
void bfs()
{
q.push(1);
vis[1]=1;
Idx=0;
while(!q.empty())
{
int x=q.front();
q.pop();
bfn[x]=++Idx;
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
if(vis[to]==0) vis[to]=1,q.push(to);
}
}
}
int Find(int x,int y)
{
if(x==y) return x;
if(check(x,son[y])) return son[y];
else while(fa[top[x]]!=y) x=fa[top[x]];
return top[x];
}
struct line
{
int x,y,w;
};
vector<line> g[MAXN];
int up[MAXN],down[MAXN],ans[MAXN],L[MAXN],R[MAXN];
void init(int x,int F)
{
ans[x]=max(ans[x],down[x]);
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
int w=ed[i]->w;
if(to==F) continue;
down[to]=max(down[to],down[x]+2*w);
init(to,x);
up[x]=max(up[x],up[to]+2*w);
};
ans[x]=max(ans[x],up[x]);
}
void calc(int x,int f,int s=-INF){
int Mx=-INF;
int sx=-INF;
ans[x]=max(ans[x],s);
for(int i=head[x];i;i=ed[i]->nxt)
{
int w=ed[i]->w;
int to=ed[i]->to;
if(to==f) continue;
if(up[to]+2*w>Mx) sx=Mx,Mx=up[to]+2*w;
else if(up[to]+2*w>sx) sx=up[to]+2*w;
}
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
int w=ed[i]->w;
if(to==f) continue;
if(up[to]+2*w==Mx) calc(to,x,max(s,sx)+2*w);
else calc(to,x,max(s,Mx)+2*w);
}
}
using namespace Segment_Tree;
void dfs(int x,int f,int s=-INF)
{
s=max(s,Getmax(1,bfn[x]));
s+=fval[x]*2;
int Mx=-INF;
int sx=-INF;
ans[x]=max(ans[x],max(w1[x],s));
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
if(w1[to]>Mx) sx=Mx,Mx=w1[to];
else if(w1[to]>sx) sx=w1[to];
}
for(auto to:g[x])
{
int to1=to.x;
int to2=to.y;
int w=to.w;
if(bfn[to1]>dfn[to2]) swap(to1,to2);
update(1,L[x],bfn[to1]-1,w);
update(1,bfn[to1]+1,bfn[to2]-1,w);
update(1,bfn[to2]+1,R[x],w);
}
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
if(to==f) continue;
if(w1[to]==Mx) dfs(to,x,max(s,sx));
else dfs(to,x,max(s,Mx));
}
}
void Init()
{
memset(up,-1 ,sizeof(up));
memset(down,-1,sizeof(down));
memset(ans,-1,sizeof(ans));
memset(w1,-1,sizeof(w1));
}
signed main()
{
Init();
n=rd();
m=rd();
for(int i=2;i<=n;i++)
{
int x =rd(),y=rd(),z=rd();
add(x,y,z);
add(y,x,z);
}
dfs1(1,0);
dfs2(1,1);
bfs();
for(int x=1;x<=n;x++)
{
L[x]=INF;
Fa[x]=x;
for(int i=head[x];i;i=ed[i]->nxt)
{
int to=ed[i]->to;
if(to==fa[x]) continue;
L[x]=min(L[x],bfn[to]);
R[x]=max(R[x],bfn[to]);
}
}
for(int i=1;i<=m;i++)
{
int x=rd(),y=rd();
int LCA=lca(x,y);
int w=dis[x]+dis[y]-2*dis[LCA];
int fx=Find(x,LCA),fy=Find(y,LCA);
if(x==y)
{
up[x]=max(up[x],0);
down[x]=max(down[x],0);
continue;
}
up[LCA]=max(up[LCA],w);
tmp=max(tmp,w);
if(LCA!=x) down[x]=max(down[x],w);
if(LCA!=y) down[y]=max(down[y],w);
if(LCA==x||LCA==y)
{
if(x==LCA) swap(x,y);
P[++pcnt]=(qr){x,y,w};
continue;
}
P[++pcnt]=(qr){x,y,w};
P[++pcnt]=(qr){y,x,w};
if(LCA!=x&&LCA!=y) g[LCA].push_back((line){fx,fy,w});
}
sort(P+1,P+pcnt+1);
for(int i=1;i<=pcnt;i++) update(P[i].x,P[i].y,P[i].w);
init(1,0);
calc(1,0);
dfs(1,0);
for(int i=1;i<=n;i++)
{
ans[i]=max(ans[i],tmp);
cout<<ans[i]<<" ";
}
return 0;
}