【比賽】【2019.8.1】

T1

不是很難,注意積雪高度的判斷(要開long long)以及終點不須要特判便可。php

#include<cstdio>
#include<cstring>

const int maxn=1e5+2;

struct Solution
{
    struct Edge{int from,to,len;};
    
    struct Graph
    {
        Edge edges[maxn*10];
        int edge_cnt,Head[maxn],Next[maxn*10];
        
        void add_edge(int from,int to,int len)
        {
            edges[++edge_cnt]=(Edge){from,to,len};
            Next[edge_cnt]=Head[from],Head[from]=edge_cnt;return;
        }
    }G;
    
    int bh[maxn],lh[maxn],ad;
    
    long long dis[maxn],vis[maxn];
    int S,T;
    
    int Q[maxn*10];
    
    void SPFA()
    {
        memset(dis,0x3f,sizeof(dis));
        dis[S]=0;int h=0,t=0;Q[t++]=S;
        
        while( h!=t )
        {
            int p=Q[h++];h%=maxn*5;
            vis[p]=0;
            
            for(int i=G.Head[p];i;i=G.Next[i])
            {
                Edge e=G.edges[i];
                
                if( ( e.to==T or bh[e.to]+ad*( (long long)dis[p]+e.len )<=lh[e.to] ) and dis[p]+(long long)e.len<dis[e.to] )
                {
                    dis[e.to]=dis[p]+e.len;
                    if( !vis[e.to] ) vis[e.to]=1,Q[t++]=e.to,t%=maxn*5;
                }
            }
        }
        
        return;
    }
    
    void solve()
    {
        int n,m,tl;scanf("%d%d%d%d%d%d",&n,&m,&S,&T,&tl,&ad);
        
        for(int i=1;i<=n;i++) scanf("%d%d",&bh[i],&lh[i]);
        
        for(int i=1;i<=m;i++)
        {
            int x,y,l;scanf("%d%d%d",&x,&y,&l);
            G.add_edge(x,y,l),G.add_edge(y,x,l);
        }
        
        SPFA();
        
        if( dis[T]<=tl ) printf("%d",dis[T]);
        else printf("wtnap wa kotori no oyatsu desu!");
        
        return;
    }
    
}SOL;

int main()
{
    SOL.solve();return 0;
}

T2

這個有點意思。算法

最短距離不難求,可是怎麼求方案數呢?數組

if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];

上面的式子不難理解,可是實際題目中的寫法必須是下面這樣:this

for(int i=1;i<=n;i++)
    for(int s=1;s<=n;s++)
        for(int t=1;t<=n;t++)
            if( s!=i and i!=t and s!=t )
            {                       
                if( dis[s][i]+dis[i][t]<dis[s][t] )
                {
                    dis[s][t]=dis[s][i]+dis[i][t];
                    ans[s][t]=ans[s][i]*ans[i][t];
                }
                else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
            }

也就是說,咱們要邊求最短路邊統計方案數spa

爲何要這樣呢?難道我不能夠求完最短路再求方案數嗎?code

不能夠get

上面的例子,算完最短路以後,就會有兩種「可行」的方案:string

  • (1-2) (2-3-4)
  • (1-2-3) (3-4)

問題在於:對於有多個節點的最短路徑,咱們要找到表示它的惟一方法it

這也就是爲何要在求最短路的時候同時求方案數。io

假設如今首先以\(2\)爲中繼點,那麼咱們就只能使\(1\to 3\)的方案數等於\(1\),至於\(2\to 4\)咱們要不已經維護過了,要不就是維護不了的(由於不能把路徑分解爲\(2\to 2\)\(2\to 4\),圖上沒有自環,並且當前\(2\to 4\)尚未被維護到)

而後,再枚舉到以\(3\)爲中繼點的時候,就能夠用\(1\to 3\)\(3\to 4\)來表示\(1\to 4\)這條路徑了。

(不用擔憂重複,重複的話意味着\(1\to 2\)\(2\to 4\)也被統計了,可是這裏以\(2\)爲中繼點的時候,咱們尚未維護\(2\to 4\)啊,並且維護完\(2\to 4\)的時候咱們也不會再去枚舉回\(2\)了,因此不會重複)

扯了這麼多,就是爲了說明:要在求最短路的同時求方案數

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn=102,maxm=9002;

struct Solution
{   
    int dis[maxn][maxn];
    long long ans[maxn][maxn];
    
    void solve()
    {
        memset(dis,0x3f,sizeof(dis));
        int n,m;scanf("%d%d",&n,&m);
    
        for(int i=1;i<=m;i++)
        {
            int x,y,l;scanf("%d%d%d",&x,&y,&l);
            
            dis[x][y]=l,ans[x][y]=1;
            dis[y][x]=l,ans[y][x]=1;
        }
        
        for(int i=1;i<=n;i++)
            for(int s=1;s<=n;s++)
                for(int t=1;t<=n;t++)
                    if( s!=i and i!=t and s!=t )
                    {                       
                        if( dis[s][i]+dis[i][t]<dis[s][t] )
                        {
                            dis[s][t]=dis[s][i]+dis[i][t];
                            ans[s][t]=ans[s][i]*ans[i][t];
                        }
                        else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
                    }
        
        for(int i=1;i<=n;i++)
        {
            double tmp=0.0;
            
            for(int s=1;s<=n;s++)
            {
                for(int t=1;t<=n;t++)
                {
                    if( s==i or i==t or s==t ) continue;
                    if( dis[s][i]+dis[i][t]==dis[s][t] ) tmp+=( (double)ans[s][i]*ans[i][t] )/ans[s][t];
                }
            }
            
            printf("%.3lf\n",tmp);
        }
        
        return;
    }
    
}SOL;

int main()
{
    SOL.solve();return 0;
}

T3

其實理解成矩陣就很好理解了:

\(A\)矩陣和\(B\)矩陣分別表示通過\(x\)條邊和\(y\)條邊的矩陣,那麼\(C=A\times B\)就是通過\(x+y\)條邊的矩陣。

JZ res;

for(int i=1;i<=idx_cnt;i++)
    for(int s=1;s<=idx_cnt;s++)
        for(int t=1;t<=idx_cnt;t++)
            res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] );

return res;

和普通的\(Floyd\)算法不一樣的是,在這裏,等式兩邊的矩陣是相互獨立的。也就是說,\(C\)矩陣中的\(dis\)不會對\(A\)\(B\)當中的產生影響,因此\(C\)矩陣是切切實實只表示\(x+y\)條邊的矩陣。

\(Floyd\)算法中,用\(1\)條邊的信息統計完\(2\)條邊的信息以後,\(2\)條邊的信息又要立刻在\(dis\)數組裏面用來統計\(3\)條、\(4\)條邊的信息,因此不是獨立的。

知道具體含義以後就能夠矩陣快速冪了:

#include<cstdio>
#include<cstring>
#include<algorithm>

int idx[1002],idx_cnt;

struct JZ
{
    int a[105][105];
    
    JZ(){memset(a,0x3f,sizeof(a));}
    
    JZ operator * (const JZ &op)
    {
        JZ res;
                
        for(int i=1;i<=idx_cnt;i++)
            for(int s=1;s<=idx_cnt;s++)
                for(int t=1;t<=idx_cnt;t++)
                    res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] );
        
        return res;
    }
    
    JZ operator *= (const JZ &op)
    {
        *this = (*this)*op;
        return *this;
    }
};

int main()
{
    JZ dis;
    
    int K,m,S,T;scanf("%d%d%d%d",&K,&m,&S,&T);
    
    for(int i=1;i<=m;i++)
    {
        int len,x,y;scanf("%d%d%d",&len,&x,&y);
        
        if( !idx[x] ) idx[x]=++idx_cnt;
        if( !idx[y] ) idx[y]=++idx_cnt;
        
        dis.a[idx[x]][idx[y]]=dis.a[idx[y]][idx[x]]=len;
    }
    
    JZ ans=dis;--K;
    
    while(K)
    {
        if( K&1 ) ans*=dis;
        dis*=dis;K>>=1;
    }
    
    printf("%d",ans.a[idx[S]][idx[T]]);
    return 0;
}

T4

最短路+計算幾何是真的神奇。

首先,那些所在直線會穿過被保護節點(把它們分紅兩部分)的線段是不能建邊的(要不就是真的穿過了,要不就是沒有穿過,可是方向不對,選了以後會多繞一條線段)

而後就要單向建邊

例如,若是對於\(\overrightarrow{AB}\),全部被保護節點都在它的右邊(也能夠與它共線),那麼就能夠直接從\(A\)連一條邊到\(B\)

其實這樣作是爲了求有向圖最小環(無向圖的太難了,偷懶嘛)。

有向圖最小環就很簡單,直接\(Floyd\)以後,\(dis(i,i)\)就是通過\(i\)點的最小環的長度。

無向圖最小環另外講。

#include<cstdio>
#include<cstring>
#include<algorithm>

const int maxn=502;

struct Solution
{
    struct Point
    {
        int x,y;
        Point(int x=0,int y=0){this->x=x,this->y=y;}
        
        void read(){scanf("%d%d",&x,&y);return;}
    };
    typedef Point Vector;
    
    Vector make_vec(Point s,Point t){return Vector(t.x-s.x,t.y-s.y);}
    int Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}
    
    Point P1[maxn],P2[maxn];
    
    int dis[maxn][maxn];
    
    bool solve()
    {
        memset(dis,0x3f,sizeof(dis));
        
        int n1;if( scanf("%d",&n1)==EOF ) return 0;
        for(int i=1;i<=n1;i++) P1[i].read();
        
        int n2;scanf("%d",&n2);
        for(int i=1;i<=n2;i++) P2[i].read();
        
        for(int s=1;s<=n2;s++)
            for(int t=1;t<=n2;t++)
            {
                if( s==t ) continue;
                
                bool flag=1;
                
                for(int k=1;k<=n1;k++)
                    if( Cross( make_vec( P2[s],P2[t] ),make_vec( P2[s],P1[k] ) )>0 ){flag=0;break;}
                
                if(flag) dis[s][t]=1;
            }
        
        for(int i=1;i<=n2;i++)
            for(int s=1;s<=n2;s++)
            {
                if( dis[s][i]==0x3f3f3f3f ) continue;
                
                for(int t=1;t<=n2;t++)
                    dis[s][t]=std::min( dis[s][t],dis[s][i]+dis[i][t] );
            }
        
        int ans=0x3f3f3f3f;
        for(int i=1;i<=n2;i++) ans=std::min( ans,dis[i][i] );;
        
        if( ans>n2 ) puts("ToT");
        else printf("%d\n",n2-ans);
        
        return 1;
    }

}SOL;

int main()
{
    while( SOL.solve() );return 0;
}
相關文章
相關標籤/搜索