题目:https://loj.ac/problem/2547

一条树边 cr->v 会被计算 ( n-siz[v] ) * siz[v] 次。一条环边会被计算几次呢?于是去写了斯坦纳树。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
int Mx(int a,int b){return a>b?a:b;}
int Mn(int a,int b){return a<b?a:b;}
const int mod=1e9+;
int upt(int x){while(x>=mod)x-=mod;while(x<)x+=mod;return x;}
int pw(int x,int k)
{int ret=;while(k){if(k&)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=;}return ret;} int n,m;
namespace S1{
const int N=,M=(<<)+;
int hd[N],xnt,to[N<<],nxt[N<<];
int bin[N],dp[N][M],dis[N][N]; bool vis[N];
queue<int> q;
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void init()
{
for(int i=;i<=n;i++)
{
memset(vis,,sizeof vis);
q.push(i); vis[i]=;
while(q.size())
{
int k=q.front(); q.pop();
for(int i=hd[k],v;i;i=nxt[i])
if(!vis[v=to[i]])
{ vis[v]=;dis[i][v]=dis[i][k]+;q.push(v);}
}
}
bin[]=;for(int i=;i<=n;i++)bin[i]=bin[i-]<<;
}
void solve()
{
for(int i=,u,v;i<=m;i++)
u=rdn(),v=rdn(),add(u,v),add(v,u);
init();
memset(dp,0x3f,sizeof dp);
for(int i=;i<=n;i++)dp[i][bin[i-]]=;
int ans=;
for(int s=;s<bin[n];s++)
{
for(int i=;i<=n;i++)
for(int t=(s-)&s;t;t=(t-)&s)
dp[i][s]=Mn(dp[i][s],dp[i][t]+dp[i][s^t]);
for(int i=;i<=n;i++)q.push(i),vis[i]=;
while(q.size())
{
int k=q.front(); q.pop(); vis[k]=;
for(int i=hd[k],v;i;i=nxt[i])
if(dp[v=to[i]][s]>dp[k][s]+)
{
dp[v][s]=dp[k][s]+;
if(!vis[v])q.push(v),vis[v]=;
}
}
int mn=dp[][s];
for(int i=;i<=n;i++)mn=Mn(mn,dp[i][s]);
ans=upt(ans+mn);
}
ans=(ll)ans*pw(bin[n],mod-)%mod;
printf("%d\n",ans);
}
}
int main()
{
n=rdn();m=rdn();
if(n<=){S1::solve();return ;}
return ;
}

不应从每条环边的角度考虑,而要从每个环的角度考虑。思维还是不足。

想算一个环上有 len 条边被选的方案。

记一个“环外子树(含自己这点)中有点被选的环上点” 为 “被选的点” 。

首先考虑如果有一个选点方案,这个环会被怎么选边。为了把点都连通,被选的点两两之间应该连通。

所以环上没被选的边一定是最远的两个相邻的被选的点之间的部分。

想求一个环 “最远相邻被选点的间隔为 len ” 的方案。注意到 “最远相邻被选点的间隔 <=len ”的方案容易 DP 。

断环成链,dp[ i ][ j ] 表示 i 点和 j 点要选,[ i , j ] 之间被选点间隔 <=len 的方案。则 \( dp[i][j]=f[j]*\sum\limits_{k=j-len}^{j-1}dp[i][k] \) ,其中 \( f[j] \) 是选 j 点的方案,即 ( 2环外子树大小 -1 ) 。

前缀和优化即可。对于一个 len ,合法的 dp[ i ][ j ] 应该满足 (cnt - j) + i <=len (cnt 是环点个数)。把这些加到 g[ len ] 上,用 (cnt-len) * (g[ len ] - g[ len-1 ]) 贡献答案即可。

求环外子树大小要小心。注意清空数组。注意分辨不同的环。可以给环最浅的点打标记,特殊求该点的环外子树大小。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int Mx(int a,int b){return a>b?a:b;}
int Mn(int a,int b){return a<b?a:b;}
const int N=,M=N<<,mod=1e9+;
int upt(int x){while(x>=mod)x-=mod;while(x<)x+=mod;return x;}
int pw(int x,int k)
{int ret=;while(k){if(k&)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=;}return ret;} int n,m,hd[N],xnt,to[M],nxt[M],spe[N];
int dp[N],pr[N],f[N],g[N],siz[N],ans,bin[N];
int tim,dfn[N],low[N],sta[N],top,cnt,col[N],qnt,q[N];
bool vis[N],ins[N];
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void tarjan(int cr,int fa)
{
dfn[cr]=low[cr]=++tim; sta[++top]=cr; ins[cr]=;
siz[cr]=;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
if(!dfn[v])
{tarjan(v,cr);low[cr]=Mn(low[cr],low[v]);siz[cr]+=siz[v];}
else if(ins[v])low[cr]=Mn(low[cr],dfn[v]);
}
if(dfn[cr]==low[cr])
{
if(sta[top]==cr){ins[cr]=;top--;return;}
cnt++;
do{int v=sta[top];ins[v]=;col[v]=cnt;}while(sta[top--]!=cr);
spe[cr]=fa;//fa can be 0 but no influence
}
}
void dfsx(int cr)
{
q[++qnt]=cr; vis[cr]=; int id=qnt;
for(int i=hd[cr],v;i;i=nxt[i])
if(!vis[v=to[i]]&&col[v]==col[cr])dfsx(v);//col==
else if(col[v]!=col[cr]&&v!=spe[cr])//include !col
f[id]+=siz[v];//col!= not !col//v!=spe[cr]
if(spe[cr])
f[id]+=n-siz[cr];//+= not =//n-siz[cr] not spe-siz[cr]
f[id]++;
}
void solve(int cr)
{
for(int i=;i<=qnt;i++)f[i]=; qnt=;//f[i]=0//qnt=0
dfsx(cr);
for(int len=;len<=qnt;len++)
{
g[len]=;
for(int i=;i<=len;i++)
{
dp[i]=bin[f[i]]; pr[i]=dp[i]; pr[i-]=;
int lm=qnt-len+i;
if(i>=lm)g[len]=upt(g[len]+dp[i]);
for(int j=i+;j<=qnt;j++)
{
dp[j]=(ll)upt(pr[j-]-pr[Mx(i,j-len)-])*bin[f[j]]%mod;
pr[j]=upt(pr[j-]+dp[j]);
if(j>=lm)g[len]=upt(g[len]+dp[j]);
}
}
}
for(int len=;len<qnt;len++)
ans=(ans+(ll)(qnt-len)*upt(g[len]-g[len-]))%mod;
}
void dfs(int cr)
{
if(col[cr]&&!vis[cr])solve(cr); ins[cr]=;//not use vis again
for(int i=hd[cr],v;i;i=nxt[i])
if(!ins[v=to[i]])
{
if(col[cr]!=col[v]||!col[cr])
//col!= not !col||!col//also !col
ans=(ans+(ll)bin[siz[v]]*bin[n-siz[v]])%mod;
dfs(v);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=,u,v;i<=m;i++)
scanf("%d%d",&u,&v),add(u,v),add(v,u);
bin[]=;for(int i=;i<=n;i++)bin[i]=upt(bin[i-]<<);
for(int i=;i<=n;i++)bin[i]=upt(bin[i]-);//
tarjan(,); dfs();
ans=(ll)ans*pw(bin[n]+,mod-)%mod;
printf("%d\n",ans);
return ;
}

LOJ 2547 「JSOI2018」防御网络——思路+环DP的更多相关文章

  1. 【LOJ】 #2547. 「JSOI2018」防御网络

    题解 如果只是一棵树的话,那么就枚举每条边,分成两部分大小为\(a\)和\(b\) 那么这条边被统计的方案数是\((2^a - 1)(2^b - 1)\) 如果是一个环的话,我们枚举环上至少有\(N ...

  2. LOJ #2547 Luogu P4517「JSOI2018」防御网络

    好像也没那么难写 LOJ #2547 Luogu P4517 题意 在一棵点仙人掌中等概率选择一个点集 求选出点集的斯坦纳树大小的期望 定义点仙人掌为不存在一个点在多个简单环中的连通图 斯坦纳树为在原 ...

  3. LOJ 2550 「JSOI2018」机器人——找规律+DP

    题目:https://loj.ac/problem/2550 只会写20分的搜索…… #include<cstdio> #include<cstring> #include&l ...

  4. LOJ 2548 「JSOI2018」绝地反击 ——二分图匹配+网络流手动退流

    题目:https://loj.ac/problem/2548 如果知道正多边形的顶点,就是二分答案.二分图匹配.于是写了个暴力枚举多边形顶点的,还很愚蠢地把第一个顶点枚举到 2*pi ,其实只要 \( ...

  5. LOJ 2551 「JSOI2018」列队——主席树+二分

    题目:https://loj.ac/problem/2551 答案是排序后依次走到 K ~ K+r-l . 想维护一个区间排序后的结果,使得可以在上面二分.求和:二分可以知道贡献是正还是负. 于是想用 ...

  6. LOJ 2546 「JSOI2018」潜入行动——树形DP

    题目:https://loj.ac/problem/2546 dp[ i ][ j ][ 0/1 ][ 0/1 ] 表示 i 子树,用 j 个点,是否用 i , i 是否被覆盖. 注意 s1<= ...

  7. LOJ 3093 「BJOI2019」光线——数学+思路

    题目:https://loj.ac/problem/3093 考虑经过种种反射,最终射下去的光线总和.往下的光线就是这个总和 * a[ i ] . 比如只有两层的话,设射到第二层的光线是 lst ,那 ...

  8. LOJ 2541 「PKUWC2018」猎人杀——思路+概率+容斥+分治

    题目:https://loj.ac/problem/2541 看了题解才会……有三点很巧妙. 1.分母如果变动,就很不好.所以考虑把操作改成 “已经选过的人仍然按 \( w_i \) 的概率被选,但是 ...

  9. LOJ 3089 「BJOI2019」奥术神杖——AC自动机DP+0/1分数规划

    题目:https://loj.ac/problem/3089 没想到把根号之类的求对数变成算数平均值.写了个只能得15分的暴力. #include<cstdio> #include< ...

随机推荐

  1. web前端之Javascript---function函数

    ---恢复内容开始--- js 支持两种函数:一类是语言内部的函数(如eval() ),另一类是自己创建的. 在 JavaScript 函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部 ...

  2. python基础学习之文件操作&函数

    1.文件处理相关 1.编码问题 ①python2与python3中的默认编码: py2默认使用ASCII码,py3默认使用utf-8 ②为什么会出现中文乱码,中文乱码的情况有哪些? #sys.stdo ...

  3. nmon的安装使用

    一.下载Nmon 根据CPU的类型选择下载相应的版本: http://nmon.sourceforge.net/pmwiki.php?n=Site.Download http://sourceforg ...

  4. node.js 之 N-blog

    N-blog  使用 Express + MongoDB 搭建多人博客 原文地址: https://github.com/nswbmw/N-blog 建议初学者,研究下整个项目. 这里节选了一些内容为 ...

  5. java面向对象编程--Josephu问题(丢手帕问题)

    Josephu问题为:设编号为1,2,...n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推 ...

  6. 微信小程序scroll-view滚动一次多次触发的问题解决方案

    最近使用微信小程序开发的时候,需要用scroll-view的bindscrolltolower事件,控制加载下一页的内容.但是发现在ios里,下拉滚动一次,事件触发两次,导致重复加载数据. 经过百度和 ...

  7. 运维ldd语法--》ldconfig

    Linux:ldd命令详解   ldd 用于打印程序或者库文件所依赖的共享库列表. 语法 ldd(选项)(参数) 选项 --version:打印指令版本号: -v:详细信息模式,打印所有相关信息: - ...

  8. dos2章

    讲FOR之前呢,咋先告诉各位新手朋友,如果你有什么命令不懂,直接在CMD下面输入: name /? 这样的格式来看系统给出的帮助文件,比如for /? 就会把FOR命令的帮助全部显示出来!当然许多菜鸟 ...

  9. re正则表达式匹配字符串中的数字

    re.match(r'.*-(\d*).html',url_1).group(1) \d+匹配1次或者多次数字,注意这里不要写成*,因为即便是小数,小数点之前也得有一个数字:\.?这个是匹配小数点的, ...

  10. SQL函数语句

    MyBatis实现模糊查询 1.${-}代替#{-} 2.把'%#{name}%'改为"%"#{name}"%" 3.使用sql中的字符串拼接函数 4.使用标签 ...