2019-7-18 考试总结

A. 星际旅行

这是一个欧拉图的题。。

因为题目说只有$2$条边走一次,剩下的$m-2$条边都走两次,

把这$m$条双向边都拆成$2$条单向边,

最后就变成了删除两条边并且让剩下的拆完的边成为一条欧拉路,(但是考试的时候想不到。。)

欧拉路指的是从起点$S$开始走所有边一次,最后走到终点$T$,$T$与$S$可以不同。

有向图中欧拉路判定就是 有且仅有$0$或$2$个点的出度和入度不相等。

只要枚举每个点$i$,在它的所有边中选择两条边删除,并且这两条边必须一个起点是$i$,一个终点是$i$。

这对答案的贡献是$\frac {n\times (n-1)}{2}$

至于为什么要除以$2$,好好看题,路径的本质不同的含义。

然后就是自环的贡献。

另外题目没说联通,所以要判一下整张图的联通性,

如果有多个子图并且这些子图中有多个子图包含的路径数大于等于$1$,

那么方案数肯定为$0$。

丑陋的代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#define Maxn 500050
#define Reg register
using namespace std;
int n,m,tot,zh,nmp,fat[Maxn],fir[Maxn],vis[Maxn],num[Maxn];
long long ans;
struct Tu {int st,ed,next;} lian[Maxn*100];
void add(int x,int y)
{
    lian[++tot].st=x;
    lian[tot].ed=y;
    lian[tot].next=fir[x];
    fir[x]=tot;
    return;
}
int getf(int x)
{
    if(fat[x]==x) return x;
    else return fat[x]=getf(fat[x]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(Reg int i=1,x,y;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        if(x==y) ++zh;
        else add(x,y),add(y,x);
    }
    for(Reg int i=1,p;i<=n;++i) fat[i]=i;
    for(Reg int i=1;i<=tot;++i)
    {
        int p=getf(lian[i].st);
        int q=getf(lian[i].ed);
        if(p!=q) fat[p]=q;
    }
    for(Reg int i=1;i<=n;++i)
    {
        int p=getf(i);
        ++num[p];
        if(!vis[p])
        {
            vis[p]=1;
            ++nmp;
        }
    }
    for(Reg int i=1,p;i<=n;++i)
    {
        p=0;
        for(Reg int j=fir[i];j;j=lian[j].next) ++p;
        ans+=(long long)p*(p-1)/2;
    }
    if(zh)
    {
        ans+=(long long)zh*(m-zh);
        ans+=(long long)zh*(zh-1)/2;
    }
    if(nmp==1) printf("%lld",ans);
    else
    {
        int ok=0;
        for(Reg int i=1;i<=n;++i)
            if(num[i]>=2) ++ok;
        if(ok==1) printf("%lld",ans);
        else printf("0");
    }
    return 0;
}
View Code

 

 

B. 砍树

很容易得到满足题目要求的一个式子:

$\sum \lceil \frac{a_i}{d}\rceil \times d-\sum a_i \leq k$

把这东西移项然后化简能得到:

$\sum \lceil \frac{a_i}{d}\rceil\leq \lfloor \frac{k+\sum a_i}{d}\rfloor$

把$k+\sum a_i$记作$C$,

那么原式就是 $\sum \lceil \frac{a_i}{d}\rceil\leq \lfloor \frac{C}{d}\rfloor$

右边的那个式子可以用数论分块的方法(虽然我不太会)

右边的那些式子是分段的,也就是在某一段区间内它的值是不会变的,

如果知道左端点的话,那么右端点是可以计算出来的,这个式子是长这个样子的: $R=\left \lfloor\frac{C}{\lfloor \frac{C}{L}\rfloor}\right \rfloor$。

左边的这个式子是单调的,随着$d$的增大,它是递减的。

那么在每一个区间内,如果有不满足上边式子的一个$d$值,小于它的$d$也肯定不成立的,

而我们要找的是$d$的最大值,那么就只需要判断每一个的右端点是否合法,

如果合法,更新答案,否则 $break$,不需要继续判断这个区间的其他的值。

这样的方法时间复杂度是$O(n\sqrt {n})$的。

丑陋的代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define min(x,y) ((x)<(y)?(x):(y))
#define Maxn 1050
#define Reg register
using namespace std;
long long n,k,ans,C,maxx;
long long num[Maxn];
bool judge(long long x,long long P)
{
    long long anp=0;
    for(Reg int i=1;i<=n;++i)
    {
        if(num[i]%x==0) anp+=num[i]/x;
        else anp+=(num[i]/x+1);
        if(anp>P) return 0;
    }
    if(anp>P) return 0;
    else return 1;
}
int main()
{
    long long P;
    scanf("%lld%lld",&n,&k); C=k;
    for(Reg int i=1;i<=n;++i)
    {
        scanf("%lld",&num[i]);
        maxx=max(maxx,num[i]);
        C+=num[i];
    }
    long long l=1,r=1;
    while(l<=maxx+k)
    {
        r=C/(C/l); P=C/l;
        if(judge(min(r,maxx+k),P)) ans=min(r,maxx+k);
        l=r+1;
    }
    printf("%lld",ans);
    return 0;
}
View Code

 

 

 

 

C. 超级树

$wd$大神博客,传送门

丑陋的代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#define Maxn 3050
#define max(x,y) ((x)>(y)?(x):(y))
#define Reg register
using namespace std;
long long sum,n,mod,dp[Maxn][Maxn];
int main()
{
    scanf("%lld%lld",&n,&mod);
    dp[1][1]=dp[1][0]=1;
    for(Reg int i=1;i<=n-1;++i)
    {
        for(Reg int j=0;j<=n-i+2;++j)
        {
            for(Reg int k=0;k<=n-i+2-j;++k)
            {
                sum=(dp[i][j]*dp[i][k])%mod;
                dp[i+1][j+k]=(dp[i+1][j+k]+sum)%mod;
                //选j+k条路径 左和右分别选的贡献
                dp[i+1][j+k+1]=(dp[i+1][j+k+1]+sum)%mod;
                //选j+k+1条路径 左和右分别选+选根的贡献
                dp[i+1][j+k]=(dp[i+1][j+k]+sum*2*(j+k))%mod;
                //选j+k条路径 左(和右)分别选一条边(起点或终点)连到根的贡献
                if(j+k-1>=0) dp[i+1][j+k-1]=(dp[i+1][j+k-1]+sum*j*k*2)%mod;
                //选j+k-1条路径 左(右)选一条边到根在连右(左)的贡献
                if(j+k-1>=0) dp[i+1][j+k-1]=(dp[i+1][j+k-1]+sum*(j*j-j+k*k-k))%mod;
                //选j+k-1条路径 左(右)连根再连左(右)的贡献
            }
        }
    }
    printf("%lld",dp[n][1]%mod);
    return 0;
}
View Code

 

 

总结

$T1$没想到欧拉回路,先用$tarjan$缩了一个点,

考场上想到的先是$tarjan$缩完点之后变成了一个森林,

对于森林的每个节点,在和它相连的边中选$2$条,然后这样更新,

最后判自环,$2$个自环,$1$个自环$1$条边。

然后树上的叶子节点的环中如果有大于$2$条边,那么它的贡献只有$2$,(当时不知道怎么想的)

最后统计$ans$输出。。

$T2$看到这个题就想到二分。。。

码了一遍二分,样例直接过,觉得自己没错,没想到它没有单调性。。。

最后$20$分(但是别人的二分都是$30$……)

$T3$没什么思路,

先打了一遍$dfs$,跑到了$4$,最后开始跑$5$的点,跑了大概半个小时。。

这样骗到$15$分。。

最后$0+20+15=35$。(第一题直接$cout<<0$可以得$20$而我爆零)

没什么水平。。。

posted @ 2019-07-20 01:26  Milk_Feng  阅读(197)  评论(1编辑  收藏  举报