暑假D22

北慕城南 提交于 2019-11-28 13:26:01

星际旅行

有n个行星由m个双向虫洞连接,不同行星间最多有1个虫洞直接相连,但一个虫洞的两端可能连接同一行星。一条星际旅行的航线满足:从任意一颗行星出发,在任意一颗行星上结束,总共经过m-2个虫洞恰2次,经过两个虫洞恰1次。两条航线不同,当且仅当至少存在一个虫洞在两条航线中经过次数不同。询问本质不同的航线有多少条。

1<=n,m<=1e5

题解

先不考虑自环,如果将边拆成两条,每个点的度数都是偶数,相当于删去两条之后能够走完所有边,这貌似就是欧拉路径,所以需要2个奇数度数的点,那么删去的边一定有且只有一个公共点,不然有4个奇数度数的点或者没有。

那么就可以找到一种答案:枚举每个点作为公共点,那么就需要选出两条边删去,方案数就是$\sum_{i=1}^{n} c(du[i],2) $,du[i]是i在原图上的度数(不算自环);

现在考虑自环,他可以作为删去的边,因为走一遍位置没变没有影响,另外一条删去的边如果也是自环的话,又有一种答案就是c(num2,2),num2为自环个数;如果另外一条边是普通边的话,在2倍边的图上随便删一条边都是欧拉路径(图上边是普通边),方案就是从技术点出发,到达自环时走一遍就继续走欧拉路径,所以又有一种答案就是 num1*num2,num1是普通边个数。

如果自环不作删去的边也没有影响,路过时走两边就好。

原图也可能不连通,不过是对于边来说,因为即使有孤点也不影响。对于边的联通判断还是可以用并查集合并点,判断就看所有是不是在一个并查集即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=100005;
int n,m,num1,num2;
int fa[maxn],du[maxn];
int xx[maxn],yy[maxn];
ll ans;

int find(int x){
    return fa[x]==x ? x : fa[x]=find(fa[x]);
}

int main(){
    freopen("tour.in","r",stdin);
    freopen("tour.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&xx[i],&yy[i]);
        if(xx[i]==yy[i]) {num2++;continue;}
        num1++;
        du[xx[i]]++;du[yy[i]]++;
        int dx=find(xx[i]),dy=find(yy[i]);
        if(dx!=dy)
            fa[dx]=dy;
    }
    bool opt=false;
    int ff=find(xx[1]);
    for(int i=2;i<=m;i++)
     if(find(xx[i])!=ff){
         opt=true;
         break;
     }
    if(opt){printf("0");return 0;}
    for(int i=1;i<=n;i++) ans+=(ll)du[i]*(du[i]-1)/2;
    ans+=(ll)num1*num2+(ll)num2*(num2-1)/2;
    printf("%lld",ans);
}
tour

砍树

林先森有n颗树苗,重在一条直线上。初始时所有树苗高度为0,每过一天长高1米。对于每颗树苗,林先森询问他的最终高度为ai,因此他会定时检查树苗的情况,并及时砍掉过高的树苗。具体来说,从种下树苗每d天林先森会检查一遍树苗,如果有树苗不低于他希望的高度,林先森会把高出的部分(可以为0)砍掉,之后这颗树苗不再长高。林先森希望砍掉的树苗的总长度不超过k米,林先森希望知道最大可能的d。

1<=n<=100,0<=k<=1e11,1<=ai<=1e9

题解

可以得到$\sum_{i=1}^{n}\left \lceil \frac{a_{i}}{d} \right \rceil*d-a_{i}<=k$

设$sum=k+\sum_{i=1}^{n}a_{i}$

那么$\sum_{i=1}^{n}\left \lceil \frac{a_{i}}{d} \right \rceil*d<=sum$

对于每个$a_{i}$,$\left \lceil \frac{a_{i}}{d} \right \rceil$,有$O(\sqrt{a_{i}}})$个,

因此$\sum_{i=1}^{n}\left \lceil \frac{a_{i}}{d} \right \rceil$只有$O(n*\sqrt{a_{i}})$种不同取法(d有这么多种取法)

那么所有的d的取值去重排序后,然后暴力计算,检验是否求出来的d是否在这个取值所要求的d的范围内,并更新答案即可。

(照着题解说,有些地方还不懂)

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn=40005;
int n,cnt;
ll a[105],ans,k;
ll d[maxn*200];

int main(){
    freopen("cut.in","r",stdin);
    freopen("cut.out","w",stdout);
    scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        k+=a[i];
    }
    for(int i=1;i<=n;i++)
     for(int j=1;j*j<=a[i];j++){
         d[++cnt]=j;
         d[++cnt]=(a[i]-1)/j+1;
     }
    sort(d+1,d+cnt+1);
    cnt=unique(d+1,d+cnt+1)-d-1;
    for(int i=1;i<=cnt;i++){
        ll ret=0;
        for(int j=1;j<=n;j++) ret+=(a[j]-1)/d[i]+1;
        if(d[i]<=k/ret) ans=k/ret;//?????
    }
    printf("%lld",ans);
}
cut

超级树

一个k-超级树可按如下方法得到:取一颗深度为k的满二叉树,对每个节点,向他的所有祖先连边(如果改边不存在),如

先统计一颗k-的超级树有多少条每个节点最多经过一次的不同有向路径。两条路径被认为不同,当且仅当它们经过的节点集合不同或者经过节点顺序不同。答案对mod取模

1<=k<=300,1<=mod<=1e9

题解

直接说做法了(反正我想不出)。

f[i][j]为i-超级树内选j条点不重复的路径方案数。

分5类,考虑递推,枚举左右子树选择条数l,r:

num=f[i-1][l]*f[i-1][r],两者互不影响所以乘法原理

1.不添加路径,直接继承f[i][l+r]+=num

2.根节点单独成为一条路径,f[i][l+r+1]+=num

3.根连接到左子树或右子树中一条路径,f[i][l+r]+=2*(l+r)*num

4.在左右子树分别选一条路径,用根连接,f[i][l+r-1]+=2*l*r*num

5.在左子树或右子树选2条路径,用根连接,f[i][l+r-1]+=num*(l*(l-1)+r(r-1))

解析代码里面有

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n,mod;
ll f[305][1025];//深度为i的超级树点不重复路径(有向)的条数为j时的方案数 

int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d%d",&n,&mod);
    f[1][0]=f[1][1]=1;
    for(int i=2;i<=n;i++){
        int lim;
        if(i<=9) lim=(1<<i)-1;
        else lim=n-i+2;
        for(int l=0;l<=lim;l++)
         for(int r=0;l+r<=lim;r++){
             ll num=f[i-1][l]*f[i-1][r]%mod;
             f[i][l+r]+=num;//什么都不做 
             f[i][l+r]+=2*(l+r)*num%mod;//根连接到左子树或者右子树,2是因为路径有向,所以连接到两端不一样
             f[i][l+r+1]+=num;//根自己作为一条路径
             f[i][l+r]%=mod;
             f[i][l+r+1]%=mod;
            if(l+r){
                f[i][l+r-1]+=2*num*l*r%mod;//在左右两颗子树分别选一条路径用根连接,连接之后两条变成一条,所以l+r-1,2是因为可以从左子树到右子树也可以右子树到左子树 
                f[i][l+r-1]+=num*(l*(l-1)+r*(r-1))%mod;//在左子树或右子树选两条路径,l+r-1同上,本身用根有2(同上)但是与c(x,2)的/2抵消了 
                f[i][l+r-1]%=mod;
            }
         }
    }
    printf("%lld",f[n][1]%mod);
}
View Code

顶不住.....

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!