[JSOI2008]最小生成树计数

[JSOI2008]最小生成树计数

Time Limit:1000MS Memory Limit:165536K
Total Submit:100 Accepted:38

Description

现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。

Input

第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。
接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。数据保证不会出现自回边和重边。
注意:具有相同权值的边不会超过10条。

Output

输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

Sample Input

Sample Output

Source
这题是老题了。。本来想A掉爽一下的,结果WA半天,最后发现还可能有无生成树的情况囧。。。
。。算法的话很显然对于所有一组权值相同的边,它们以最大数量加入之后,造成的连通性是一样的,那么从小到大算出每一组边,乘起来就OK了。。
如果数据大一点的话只好用矩阵来算答案了。。不过这里范围这么小暴力就OK了。。。
Code:
#include <vector>

#include <iostream>
#include <map>
#include <utility>
#define rep(i,n) for(int i=0;i<n;i++)
#define pb push_back
const int mod=31011,maxn=100,maxm=1000;
using namespace std;
typedef pair<int,int> ii;
struct UF
{
    int F[maxn];
    void clear(){rep(i,maxn)F[i]=i;}
    int find(int x)
    {
        if(F[x]==x)return x;
        return F[x]=find(F[x]);
    }
    bool Union(int i,int j)
    {
        i=find(i);j=find(j);
        return F[i]=j,i!=j;
    }
    UF& operator=(const UF&u)
    {
        memcpy(F,u.F,sizeof(F));
        return *this;
    }
};
int n,m,ans=1,get,ret;
typedef map<int,vector<ii> >::iterator mit;
map<int,vector<ii> > M;
UF All,Link;
vector<ii> now;
void Dfs(int p,int c)
{
    if(p==now.size())
    {
        if(c==get)ret++;
        return;
    }
    ii t=now[p];UF tmp=Link;
    if(Link.Union(t.first,t.second))Dfs(p+1,c+1);
    Link=tmp;
    Dfs(p+1,c);
}
int main()
{
    cin>>n>>m;int s,t,c;
    rep(i,m)
    {
        cin>>s>>t>>c;s–;t–;
        M.pb(ii(s,t));
    }
    All.clear();s=0;
    for(mit i=M.begin();i!=M.end();i++)
    {
        now=i->second;Link=All;get=0;ret=0;
        rep(j,now.size())get+=All.Union(now[j].first,now[j].second);
        Dfs(0,0);ans*=ret;ans%=mod;s+=get;
    }
    if(s<n-1)cout<<0<<endl;
    else cout<<ans<<endl;
}

84 61 2 11 3 11 4 12 3 22 4 13 4 1

510. Distinct Substrings

给你一个N,让你求有恰有N个不同字串的,尽可能短的,如有多个就字典序最小的那个的字符串。。。
我感觉没有任何思路,只能暴搜。。
肯定要减枝,就是估计一个当前以当前字符串为前缀的最大可能的不同子串数。。
我不知道对不对,我是在后面defghijk这么加上去再算不同字串数的。。
计算不同字串数的话题目中也说是标准的SA问题,但我直接用Set了。。
Code:

#include<cstring>#include<iostream>#include<string>#include<cstdlib>#include<set>#define Rep(i,n) for(int i=0;i<n;i++)using namespace std;int n;string C;int Get(const string&a){ set<string> S;int n=a.size(); for(int i=0;i<n;i++) for(int j=1;i+j<=n;j++) S.insert(a.substr(i,j)); return S.size();}int Cal(int p,char u){ for(int i=p;i<C.size();i++) C[i]=++u; return Get(C);}void Dfs(int p,char u){ int N=Cal(p,u); if(N<n)return; if(p==C.size()) { if(N==n) { cout<<C<<endl; exit(0); } return; } for(char a=’a’;a<=u;a++) { C[p]=a; Dfs(p+1,u); } if(u<‘z’){C[p]=++u;Dfs(p+1,u);}}int main(){ cin>>n; for(int i=1;;i++) { C.resize(i); Dfs(0,’a’-1); }}

SGU 505. Prefixes and suffixes

就是给你一大堆字符串,然后每次询问以一个给定串为前缀,
一个给定串为后缀的字符串有几个?
我是一直没有思路的,但是在做出网易比赛的拿到有道搜索框之后
我就已经知道算法了。。但是
一直WA,今天才发现是犯了个SB错误囧啊。。。
是这样的,将所有字符串排序,由那题我明白了所有由某个前缀的字符串是一个区间,
然后将所有字符串
全部反过来排序,那么由某个后缀的字符串也是一个区间,把这些字符串都标上其在
第一遍排序中的排名。。那么问题就成了,在[a,b]中,[l,r]之间的数有几个。。
那么由两种办法,一种是在线算法,就是建个线段树,那么是LogN^2的。。
另一种就是离线算法,只需要一个树状数组就OK了。。。
鉴于第一个可能TLE。。所以我用了离线算法。。编写上需要注意一些东西。。
我发现关于string的话,cin还是蛮快的。。
Code:
include<string>
#include<algorithm>
#include<iostream>
#include<vector>
#include<cstring>
#include<cstdio>
#define pb push_back
#define Rep(i,n) for(int i=0;i<n;i++)
#define All(x) x.begin(),x.end()
#define tr(e,x) for(typeof(x.begin()) e=x.begin();e!=x.end();e++)
using namespace std;
const int maxn=100000,maxm=100000;
string S[maxn],R[maxn];
int n,m,Ans[maxm]={},A[maxn];
struct Data
{
    int l,r,num,d;
    Data(int _l,int _r,int _num,int _d):l(_l),r(_r),num(_num),d(_d){}
};
vector<Data> E[maxn];
bool AsPrefix(const string&str,const string&prefix)
{
    if(str.size()<prefix.size()||str.substr(0,prefix.size())!=prefix)
        return false;
    return true;
}
void get(string S[maxn],string pre,int&l,int&r)
{
    l=lower_bound(S,S+n,pre)-S;
    pre[pre.size()-1]++;
    r=lower_bound(S,S+n,pre)-S;
}
struct TreeArray
{
    int H[maxn+10];
    TreeArray(){memset(H,0,sizeof(H));}
    void Add(int l,int d)
    {
        l++;
        for(;l<=n;l+=(l&-l))
            H[l]+=d;
    }
    int operator[](int l)
    {
        l++;int ret=0;
        for(;l>0;l-=(l&-l))
            ret+=H[l];
        return ret;
    }
}T;
int main()
{
    //freopen("in","r",stdin);
    cin>>n;Rep(i,n)cin>>S[i],R[i]=S[i],reverse(All(R[i]));
    sort(S,S+n);sort(R,R+n);
    Rep(i,n)
    {
        string tmp=R[i];reverse(All(tmp));
        A[i]=lower_bound(S,S+n,tmp)-S;
    }
    cin>>m;string Pre,Suf;
    Rep(i,m)
    {
        cin>>Pre>>Suf;
        int l,r,a,b;
        get(S,Pre,l,r);
        if(l==r)continue;
        reverse(All(Suf));
        get(R,Suf,a,b);
        if(a==b)continue;
        E[b-1].pb(Data(l,r-1,i,1));
        if(a)E[a-1].pb(Data(l,r-1,i,-1));
    }
    Rep(i,n)
    {
        T.Add(A[i],1);
        tr(e,E[i])Ans[e->num]+=(T[e->r]-T[e->l-1])*e->d;
    }
    Rep(i,m)printf("%dn",Ans[i]);
}

[SDOI2009]SuperGCD

[SDOI2009]SuperGCD

Time Limit:4000MS Memory Limit:65536K
Total Submit:34 Accepted:6
Case Time Limit:1000MS

Description

Sheng bill有着惊人的心算能力,甚至能用大脑计算出两个巨大的数的GCD(最大公约
数)!因此他经常和别人比赛计算GCD。有一天Sheng bill很嚣张地找到了你,并要求和你比
赛,但是输给Sheng bill岂不是很丢脸!所以你决定写一个程序来教训他。

Input

共两行:
第一行:一个数A。
第二行:一个数B。

Output

一行,表示A和B的最大公约数。

Sample Input

Sample Output

Hint

对于20%的数据,0 < A , B ≤ 10 ^ 18。
对于100%的数据,0 < A , B ≤ 10 ^ 10000。

Source

Day1
总算AC了这题,感谢GCD。。。
然后是算法。。。TMD八中OJ居然没有Java。。。。只好自己写高精度,算法是这样的。。
设这两个数是A,B
那么若2|A和B (A,B)=2(A/2,B/2)。。
若2|A且2不整除B, (A,B)=(A/2,B)..
若A和B都是奇数,设A<B,(A,B)=(A,B-A)…
关键是一般的高精度超时很严重啊。。
于是压位,我压了10位。。结果因为Long Long的问题WA了几次。。。
然后把所有的2放在一起乘。。。
就很快了。。。
Orz。sevenk神牛。。
Code:

#include<cstdio>#include<cstring>#include<iostream>#define Rep(i,n) for(int i=0;i<n;i++)using namespace std;typedef unsigned long long ull;const int maxn=10000+10,L=10;const ull m=1e10;char C[maxn+10];ull P[L];struct BigInt{ ull H[maxn/L+1];int l; BigInt() { memset(H,0,sizeof(H));l=0; } void divide() { ull d=0; for(int i=l;i>=0;i–) { d*=m;d+=H[i]; H[i]=d/2;d%=2; } while(l&&!H[l])l–; } int mod() { return H[0]%2; } void operator*=(int x) { ull d=0; for(int i=0;i<=l;i++) { d+=H[i]*x;H[i]=d%m; d/=m; } while(d)H[++l]=d%m,d/=m; } void operator-=(const BigInt&o) { ull d=0; for(int i=0;i<=l;i++) { H[i]-=d; if(H[i]<o.H[i])H[i]+=m,d=1; else d=0; H[i]-=o.H[i]; } while(l&&!H[l])l–; } void ReadIn() { memset(C,0,sizeof(C)); scanf("%s",C);int s=0;l=0;ull a=0; for(int i=strlen(C)-1;i>=0;i–) { a+=(C[i]-‘0’)*P[s++]; if(s==L)H[l++]=a,s=0,a=0; } if(!s)l–;else H[l]=a; } void Print() { printf("%I64d",H[l]); for(int i=l-1;i>=0;i–)printf("%010I64d",H[i]); } bool operator<(const BigInt&o)const { if(l!=o.l)return l<o.l; for(int i=l;i>=0;i–) { if(H[i]<o.H[i])return true; if(H[i]>o.H[i])return false; } } bool iszero()const{return l==0&&H[l]==0;}}A[2];int main(){ freopen("in","r",stdin); P[0]=1;Rep(i,L-1)P[i+1]=P[i]*10; A[0].ReadIn();A[1].ReadIn(); int a=0,b=1,c=0; while(true) { if(A[b]<A[a])a^=b^=a^=b; //A[a].Print();printf(",");A[b].Print(); //puts(""); if(A[a].iszero()) { while(c>=16)A[b]*=(1<<16),c-=16; while(c–)A[b]*=2; A[b].Print(); break; } int i=A[a].mod(),j=A[b].mod(); if(!i)A[a].divide(); if(!j)A[b].divide(); if(!i&&!j)c++; if(i&&j)A[b]-=A[a]; }}61254

网易有道资格赛1-最大和子序列

我个NC。。这道题代码量实际上很小而且也不是很triky。。比赛的时候我居然悲剧囧死了。。。。
就是原来的算法,首先由于每个都是非空的那么特判一下正数小于等于2个情况(此时显然取最大的两个)。。然后先算没有跨越环的,设L[x]表示1-x的最大子序列长度,R[x]表示x-n的最大子序列长度,答案很显然就是Max(L[x]+R[x+1])。。。然后如果跨越环了,那么它的对偶问题就是不选的那些肯定不跨越环,一样的DP可以算出来(为了方便把所有值取负就OK了。。)。。两个中的最大值就是答案了。。。。
Code:
#include<cstdio>#include<algorithm>#define rep(i,n) for(int i=0;i<n;i++)using namespace std;const int maxn=50000+10,inf=~0U>>1;int t,n,A[maxn],L[maxn],R[maxn];int Cal(){ L[0]=0; for(int i=1;i<=n;i++) L[i]=max(L[i-1],0)+A[i]; R[n+1]=0; for(int i=n;i>=1;i–) R[i]=max(R[i+1],0)+A[i]; for(int i=1;i<=n;i++)L[i]=max(L[i],L[i-1]); for(int i=n;i>=1;i–)R[i]=max(R[i],R[i+1]); int ans=-inf; for(int i=1;i<=n;i++)ans=max(ans,L[i]+R[i+1]); return ans;}void Solve(){ scanf("%d",&n);int z=0,s=0,a=-inf,b=-inf; rep(i,n)scanf("%d",A+i+1),z+=A[i+1]>0,s+=A[i+1]; if(z<=2) { rep(i,n) { if(A[i+1]>b)b=A[i+1]; if(a<b)swap(a,b); } printf("%dn",a+b); return; } int ans=Cal(); rep(i,n)A[i+1]=-A[i+1]; ans=max(ans,s+Cal()); printf("%dn",ans);}int main(){ //freopen("in","r",stdin); int t;scanf("%d",&t); rep(i,t)Solve();}

网易有道资格赛-1

第一题直接暴力算就OK了。。
第二题先把所有字符串排序,然后查找就OK了。。。
第三题真是太狗血了。算法我一下就想到了但因为脑子出问题一直WA(F..K啊!!!)
实际上如果不跨越环的话,一个O(N)的DP就可以解决问题,如果跨越环的话,那么考虑这个问题的对偶就是取两段最小的(那么出这些之外的也是两段且是最大的)就不会跨越环了。。
但见鬼的是由于两段都要非空,所以第一个要求至少取两个,第二个要求剩下的至少有2个。。就很难处理了。。我是个NC。。所以一直WA。。痛苦死了。。
太失败了。。总结一下。。首先是脑子很乱的时候如果再急一下就大悲剧了。。
对于难题算法的思考还是不够细致和冷静而导致悲剧。。现在我明白为什么我的代码会错了。。
犯了一个超低级的错误。。晕死。。还有就是错误点不一定是你一直以为的,
比如我一直以为是非空的处理错了,实际上之后几次已经改对了,但DP的时候犯了个小错误导致悲剧。。。脑子一片空白就不要写了。。休息一下什么的,否则也不会把DP写错了囧。。
还有就是C是有朴素算法,为什么不先写个对拍呢?真是NC啊囧。。。
弄测试数据的时候不要总想着极端的,极端只能测试一些点有没有处理正确,不能测试算法是否整体正确啊。。
哎。。总而言之言而总之我是个大大的NC、SB、弱菜。。
但神奇的是难道是我信春哥春哥保佑我么?前400晋级最后我正好398名。。。无语了。。

[SCOI2009]windy数

[SCOI2009]windy数

Time Limit:1000MS  Memory Limit:165536K
Total Submit:118 Accepted:54

Description

windy定义了一种windy数。
不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。
windy想知道,在A和B之间,包括A和B,总共有多少个windy数?

Input

包含两个整数,A B。

Output

一个整数。

Sample Input

【输入样例一】
1 10

【输入样例二】
25 50

Sample Output

【输出样例一】
9

【输出样例二】
20

【数据规模和约定】
20%的数据,满足 1 <= A <= B <= 1000000 。
100%的数据,满足 1 <= A <= B <= 2000000000 。

Source

Day1
哎。。还是数位统计问题,幸好对付这种题目可以写个暴力程序来对拍,不然肯定要悲剧啊囧。。。有经验了。。总结一下:
首先要注意第一位就是0的情况,着并不代表这位是0,而是忽略这位,所以按照公式写个特殊的统计的就OK了。。
还有就是可以前面的数位已经不能是windy数了,就不要推下去了。。
同时一定要写个暴力对拍。。
对拍要多考虑这样的数据:
1: 1-1000000这样的.。。
2:1-99999这样的。。
3: xxx-xxxxx这样的,至少差2位。。
4:如果还不放心随机个N组来搞搞。。
5:最好用long long免得溢出。。
6:没有了。。

对拍器:

#include <vector>
#include <algorithm>
#include <utility>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <sstream>
#define rep(i,n) for(int i=0;i<n;i++)
#define pb push_back
const int inf=~0U>>1;
using namespace std;
typedef ostringstream OSS;
int C[10];
bool Check(int a)
{
    OSS t;
    t<<a;
    string s=t.str();
    rep(i,s.size()-1)if(abs(s[i]-s[i+1])<2)return false;
    return true;
}
int main()
{
    int a,b,c=0;
    cin>>a>>b;
    for(int i=a; i<=b; i++)c+=Check(i);
    cout<<c<<endl;
}

代码:

#include <vector>
#include <algorithm>
#include <utility>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#define rep(i,n) for(int i=0;i<n;i++)
#define pb push_back
const int inf=~0U>>1,maxd=12;
using namespace std;
typedef long long ll;
ll Count[maxd][10]= {},C;
int N[maxd],n;
void PreDo()
{
    rep(j,10)Count[1][j]=1;
    for(int i=2; i<maxd; i++)
    {
        rep(j,10)
        {
            Count[i][j]=0;
            rep(k,10)if(abs(k-j)>=2)Count[i][j]+=Count[i-1][k];
        }
    }
}
void Dfs(int p,int l)
{
    int t=N[p];
    if(p==0)
    {
        rep(j,t+1)if(abs(j-l)>=2)C++;
        return;
    }
    rep(i,t)
    {
        if(abs(i-l)>=2)
        {
            if(p==n-1&&i==0)
            {
                rep(j,p)rep(k,9)C+=Count[j+1][k+1];
            }
            else
            {
                C+=Count[p+1][i];
            }
        }
    }
    if(abs(t-l)>=2)Dfs(p-1,t);
}
ll Cal(ll A)
{
    if(!A)return 0;
    for(n=0; A; A/=10,n++)N[n]=A%10;
    C=0;
    Dfs(n-1,20);
    return C;
}
int main()
{
//freopen("in","r",stdin);
    PreDo();
    ll a,b;
    cin>>a>>b;
    cout<<Cal(b)-Cal(a-1)<<endl;
}

[Scoi2010]传送带

[Scoi2010]传送带

Time Limit:1000MS  Memory Limit:65536K
Total Submit:48 Accepted:30

Description

在一个2维平面上有两条传送带,每一条传送带可以看成是一条线段。两条传送带分别为线段AB和线段CD。lxhgww在AB上的移动速度为P,在CD上的 移动速度为Q,在平面上的移动速度R。现在lxhgww想从A点走到D点,他想知道最少需要走多长时间

Input

输入数据第一行是4个整数,表示A和B的坐标,分别为Ax,Ay,Bx,By
第二行是4个整数,表示C和D的坐标,分别为Cx,Cy,Dx,Dy
第三行是3个整数,分别是P,Q,R

Output

输出数据为一行,表示lxhgww从A点走到D点的最短时间,保留到小数点后2位

Sample Input

0 0 0 100
100 0 100 100
2 2 1

Sample Output

136.60

Hint

对于100%的数据,1<= Ax,Ay,Bx,By,Cx,Cy,Dx,Dy<=1000
1<=P,Q,R<=10

Source

Day2
很显然正解是双重二分之类的,不过我用模拟退火A掉了囧。。随机化无敌!!!
Code:

#include <vector>
#include <algorithm>
#include <utility>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#define rep(i,n) for(int i=0;i<n;i++)
#define pb push_back
const int inf=~0U>>1;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
double P,Q,R;
struct Point
{
double x,y;
Point(){}
Point(double _x,double _y):x(_x),y(_y){}
Point operator*(double d)
{
return Point(x*d,y*d);
}
Point operator+(const Point&o)const
{
return Point(x+o.x,y+o.y);
}
};
struct Line
{
Point A,B;
Line(){}
Point get(double d)
{
return A*d+B*(1-d);
}
}A,B;
istream& operator>>(istream&in,Point&p)
{
in>>p.x>>p.y;
return in;
}
istream& operator>>(istream&in,Line&l)
{
in>>l.A>>l.B;
return in;
}
struct State
{
double Up,Down;
State(){}
State(double _Up,double _Down):Up(_Up),Down(_Down){}
void Just(double a,double b)
{
Up+=a;Down+=b;
}
};
double Dist(const Point&a,const Point&b)
{
static double A,B;
A=a.x-b.x,B=a.y-b.y;
return sqrt(A*A+B*B);
}
double Cal(State s)
{
Point a=A.get(s.Up),b=B.get(s.Down);
return Dist(a,A.A)/P+Dist(a,b)/R+Dist(b,B.B)/Q;
}
double rand_double()
{
return (double(2*rand())/RAND_MAX)-1;
}
double check_Legal(State s)
{
return s.Up>=0&&s.Up<=1&&s.Down>=0&&s.Down<=1;
}
int main()
{
//freopen("in","r",stdin);
cin>>A>>B>>P>>Q>>R;
State now(0,0),tmp;double min=Cal(now);
for(double e=1;e>1e-10;e/=2)
{
int time=1000;
while(time–)
{
tmp=now;tmp.Just(rand_double()*e,rand_double()*e);
if(!check_Legal(tmp))continue;
double new_min=Cal(tmp);
if(new_min<min)
{
min=new_min;now=tmp;
}
}
}
printf("%0.2lfn",min);
}

[ZJOI2010]count 数字计数

[ZJOI2010]count 数字计数

Time Limit:3000MS  Memory Limit:65536K
Total Submit:128 Accepted:63
Case Time Limit:1000MS

Description

给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。

Input

输入文件中仅包含一行两个整数a、b,含义如上所述。

Output

输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。

Sample Input

1 99

Sample Output

9 20 20 20 20 20 20 20 20 20

Hint

30%的数据中,a<=b<=10^6;
100%的数据中,a<=b<=10^12。

Source

Day1
我的娘啊,这题搞死我了。。
数位统计真是恶心囧。。。
算法差不多吧,就是一位位统计过去,最关键的一点是处理好第一位为0的情况。。我的办法是首先看成0000-9999之类的,然后减掉多余的0囧。。

#include <vector>
#include <algorithm>
#include <utility>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#define rep(i,n) for(int i=0;i<n;i++)
#define pb push_back
const int inf=~0U>>1,maxn=10,maxd=15;
using namespace std;
typedef long long ll;
ll A[maxn],C[maxn],Pow[maxd],Pre[maxn];
int N[maxd],n;
void Dfs(int p)
{
int t=N[p];
if(p<0)
{
rep(j,10)C[j]+=Pre[j];
return;
}
rep(i,t)
{
ll num=Pow[p];
if(p==n-1&&i==0)
{
rep(j,10)C[j]+=num/10*p;
rep(j,p+1)
{
C[0]-=(Pow[p-j]-Pow[p-j]/10)*j;
}
}
else
{
Pre[i]++;
rep(j,10)C[j]+=Pre[j]*num;
Pre[i]–;num/=10;
rep(j,10)C[j]+=p*num;
}
}
Pre[t]++;Dfs(p-1);
}
void Cal(ll x)
{
for(n=0;x;x/=10,n++)N[n]=x%10;
memset(C,0,sizeof(C));
memset(Pre,0,sizeof(Pre));
Dfs(n-1);
}
int main()
{
//freopen("in","r",stdin);
//freopen("out","w",stdout);
ll a,b;cin>>a>>b;Pow[0]=1;rep(i,maxd-1)Pow[i+1]=Pow[i]*10;
Cal(b);rep(i,10)A[i]=C[i];
Cal(a-1);rep(i,10)A[i]-=C[i];
rep(i,10)cout<<A[i]<<" ";
}

[BeiJing2006]狼抓兔子

[BeiJing2006]狼抓兔子

Time Limit:15000MS  Memory Limit:165536K
Total Submit:762 Accepted:164
Case Time Limit:4000MS

Description

现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对 下面这样一个网格的地形:


左上角点为(1,1),右下角点为(N,M)(上图中N=4,M=5).有以下三种类型的道路
1:(x,y)<==>(x+1,y)
2:(x,y)<==>(x,y+1)
3:(x,y)<==>(x+1,y+1)
道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的.
左上角和右下角为兔子的两个窝,开始时所有的兔子都聚集在左上角(1,1)的窝里,现在它们要跑到右下解(N,M)的窝中去,狼王开始伏击这些兔 子.当然为了保险起见,如果一条道路上最多通过的兔子数为K,狼王需要安排同样数量的K只狼,才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使 得在将兔子一网打尽的前提下,参与的狼的数量要最小。因为狼还要去找喜羊羊麻烦.

Input

第一行为N,M.表示网格的大小,N,M均小于等于1000.接下来分三部分
第一部分共N行,每行M-1个数,表示横向道路的权值.
第二部分共N-1行,每行M个数,表示纵向道路的权值.
第三部分共N-1行,每行M-1个数,表示斜向道路的权值.
输入文件保证不超过10M

Output

输出一个整数,表示参与伏击的狼的最小数量.

Sample Input

3 4
5 6 4
4 3 1
7 5 3
5 6 7 8
8 7 6 5
5 5 5
6 6 6

Sample Output

14

Source
这题一看就是最小割问题,但范围怎么这么大囧。。。后来看了周冬神牛的论文(2008年的那个)才知道原来平面图的最小割等于对偶图的最短路囧。。构图很麻烦,搞得我累死囧。。

#include<cstdio>
#include<vector>
#include<queue>
#include<iostream>
#define rep(i,n) for(int i=0;i<n;i++)
#define pb push_back
#define tr(e,x) for(eit e=x.begin();e!=x.end();e++)
using namespace std;
const int maxn=1000,maxv=maxn*maxn*2,inf=~0U>>2;
int n,m,vs,vt,v;
int Node[maxn][maxn][2];
int Dist[maxv];
struct Edge
{
int t,c;
Edge(int _t,int _c):t(_t),c(_c){}
};
struct State
{
int p,c;
State(int _p,int _c):p(_p),c(_c){}
bool operator<(const State&o)const
{
return c>o.c;
}
};
vector<Edge> E[maxv];
priority_queue<State> Q;
typedef vector<Edge>::iterator eit;
int Dijstra()
{
rep(i,v)Dist[i]=inf;Q.push(State(vs,0));
while(Q.size())
{
State t=Q.top();Q.pop();if(t.c>Dist[t.p])continue;
if(t.p==vt)return t.c;
tr(e,E[t.p])
{
int ncost=t.c+e->c;
if(ncost<Dist[e->t])
{
Dist[e->t]=ncost;
Q.push(State(e->t,ncost));
}
}
}
}
void AddEdge(int s,int t,int c)
{
E[s].pb(Edge(t,c));
E[t].pb(Edge(s,c));
}
int main()
{
//freopen("in","r",stdin);
scanf("%d%d",&n,&m);v=0;
rep(i,n-1)rep(j,m-1)rep(k,2)Node[i][j][k]=v++;
vs=v++;vt=v++;int c;
rep(j,m-1)scanf("%d",&c),AddEdge(vs,Node[0][j][0],c);
rep(i,n-2)rep(j,m-1)
{
scanf("%d",&c);AddEdge(Node[i][j][1],Node[i+1][j][0],c);
}
rep(j,m-1)scanf("%d",&c),AddEdge(Node[n-2][j][1],vt,c);
rep(i,n-1)
{
scanf("%d",&c);AddEdge(Node[i][0][1],vt,c);
rep(j,m-2)
{
scanf("%d",&c);AddEdge(Node[i][j+1][1],Node[i][j][0],c);
}
scanf("%d",&c);AddEdge(vs,Node[i][m-2][0],c);
}
rep(i,n-1)
rep(j,m-1)
{
scanf("%d",&c);
AddEdge(Node[i][j][0],Node[i][j][1],c);
}
printf("%dn",Dijstra());
}