tc-srm704-div1-1000 解题报告

10 篇文章 1 订阅
2 篇文章 0 订阅

题意:构造一个点数不超过20无重边无自环的有向图,节点编号从0到n-1,使得从0到n-1的哈密顿路径数量恰好为k。

感觉完全没有思路。。不知道怎么构造。
去看了下别人的代码,原来可以做成1<-2<-…<-18一条路径,然后1-18都向编号比它大的节点连边。这样的话往回走就只有唯一的路径,所以假如说我当前在i,走到了j,j>i,那么就需要立刻从j到j-1直到i+1,然后再往后走。这样就相当于每个节点有选或不选两种状态,如果选的话就是从前面第一个比它小的选的节点跳过来,不选的话就是从i+1过来。所以如果有0->i,就相当于贡献了 218i11i<18) (因为18是必选的,19不能回到18)。

既然不会做的话,那就乱搞一下吧~
假如说随便确定了一个图,怎么求路径数量呢?那就需要 O(220202) 的状压dp。不过我们可以先不求0->n-1的哈密顿路径数量,可以先求 i>19,1i<19 ,然后再连从0出去的边,这样的话就相当于是问是否存在一个子集和恰好为k。
我们知道 (189) 是一个非常大的数了,所以如果i->19的路径数量是在 k9 附近随机,那么就很容易一不小心就凑出个k来!所以我们可以二分边的总数,直到找到一个边的总数在那里随机 i>19 的路径数量的平均值会离 k9 比较近,然后就在那里一直随机。
这样的话时间复杂度是 O(2nn2logn)109 的。所以dp的常数比较要用心卡一下才行。。。我一开始算错了时间复杂度以为是 O(2nnlogn) 的就没管,结果样例都过不去。。。

代码:

#include<bits/stdc++.h>
using namespace std;
const int n=20,N=20;
typedef long long LL;
class HamiltonianConstruction
{
    public:
        pair<int,int> edge[N*N];
        int etot;
        LL f[1<<N][N];
        LL s[1<<N];
        int prev[N];
        int bit[1<<N];
        inline void cal(int mid)
        {
            random_shuffle(edge,edge+etot);
            memset(prev,0,sizeof(prev));
            for(int i=mid;i--;)prev[edge[i].second-1]|=1<<edge[i].first-1;
            memset(f,0,sizeof(f));
            f[1<<n-2][n-2]=1;
            for(int i=1;i<1<<n-1;++i)
                for(int j=i,x;j;j^=1<<x)
                    if(f[i][x=bit[j&-j]])
                        for(int k=prev[x]&~i,y;k;k^=y)
                        {
                            y=k&-k;
                            f[i|y][bit[y]]+=f[i][x];
                        }
            for(int i=n-2;i--;)s[1<<i]=f[(1<<n-1)-1][i];
            for(int i=1;i<1<<n-2;++i)s[i]=s[i^i&-i]+s[i&-i];
        }
        inline vector<string> construct(int k)
        {
            for(int i=0;i<n;++i)bit[1<<i]=i;
            for(int i=n-1;--i;)
                for(int j=n;--j;)
                    if(i!=j)
                        edge[etot++]=make_pair(i,j);
            vector<string> ans(N,string(N,'N'));
            int l=0,r=100;
            while(r-l>1)
            {
                int mid=l+r>>1;
                cal(mid);
                for(int i=1<<n-2;i--;)
                    if(s[i]==k)
                    {
                        for(int j=mid;j--;)ans[edge[j].first][edge[j].second]='Y';
                        for(int j=n-2;j--;)
                            if(i>>j&1)
                                ans[0][j+1]='Y';
                        return ans;
                    }
                LL sum=0;
                for(int i=n-2;i--;)sum+=s[1<<i];
                if(sum>=k<<1)r=mid;
                else l=mid;
            }
            for(;;)
            {
                cal(r);
                for(int i=1<<n-2;i--;)
                    if(s[i]==k)
                    {
                        for(int j=r;j--;)ans[edge[j].first][edge[j].second]='Y';
                        for(int j=n-2;j--;)
                            if(i>>j&1)
                                ans[0][j+1]='Y';
                        return ans;
                    }
            }
        }
};

总结:
①构造的时候可以考虑一些特殊情况,或者根据题目中的特殊性质。比如说构造一个图,就可以先考虑一条链、完全图、树。
②如果没有构造的思路,不妨试试随机化!
③一定要算好时间复杂度!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值