1. 程式人生 > >[2019.3.21]洛谷P3640 [APIO2013]出題人

[2019.3.21]洛谷P3640 [APIO2013]出題人

存在 modified 突發奇想 更新 發現 答案 trac code 一道

突發奇想做一道非傳統題。。。

只要發現這些算法的一些性質就好了:

SSSP:

FloydWarshall:穩定\(O(V^3)\)

ModifiedDijkstra:正權圖跑得賊快,負權圖可能會被卡掉

OptimizedBellmanFord:時間按復雜度取決於更新節點的順序

Mystery(染色問題):

RecursiveBacktracking:時間復雜度很大程度上取決於答案的大小

Task1

既然ModifiedDijkstra在正權圖上跑得飛快,我們只需要構造正權圖卡掉FloydWarshall即可。

由於FloydWarshall是穩定\(V^3\)的,只要出到\(V>100\)就行了,所有點的出邊數量可以為0。

(允許的話甚至可以沒有詢問,但是本題要求\(Q>0\))

code:

#include<bits/stdc++.h>
using namespace std;
int n=101;
int main(){
    freopen("1.txt","w",stdout),printf("%d\n",n);
    for(int i=1;i<=n;++i)puts("0");
    puts("1\n1 1");
    return 0;
}

Task2

由於FloydWarshall穩定\(V^3\)

,我們考慮讓\(V=100\),這樣FloydWarshall永遠也不會被卡掉,又給我們卡OptimizedBellmanFord提供了最方便的條件。

發現OptimizedBellmanFord是\(O(V^2E)\)的,但是由於剪枝的存在,可能跑不滿。

如何讓它跑滿呢?

發現它更新節點的順序是從0到\(V-1\),所以我們只需要建一條鏈,\(V-1\)為起點,0為終點就好了。

但是還不夠,由於點數有限,導致鏈的長度有限,而且給定的\(T\)遠遠沒用完。

那麽把剩下的\(T\)建成重邊就好了。

code:

#include<bits/stdc++.h>
using namespace std;
int n=100,T=2222,Q=10,Num,RE,RN;
void print(int x){
    if(x!=n-1){
        printf("%d ",Num);
        for(int i=1;i<=Num;++i)printf("%d 1 ",x?x-1:n-1);//0與n-1之間的邊並不會影響復雜度
        puts("");
    }else{
        printf("%d ",Num+RE);
        for(int i=1;i<=Num+RE;++i)printf("%d 1 ",x?x-1:n-1);
        puts("");
    }
}
int main(){
    freopen("2.txt","w",stdout);
    printf("%d\n",n),T-=1+n+1+(Q<<1),Num=(T>>1)/n,RE=(T>>1)-Num*n;
    for(int i=0;i<n;++i)print(i);
    printf("%d\n",Q);
    while(Q--)printf("%d 0\n",n-1);
    return 0;
}

Task3

由於FloydWarshall穩定\(V^3\),直接令\(V>100\)就可以直接卡掉,放OptimizedBellmanFord過也很簡單,不連邊就好了。

code:

#include<bits/stdc++.h>
using namespace std;
int n=101;
int main(){
    freopen("3.txt","w",stdout);
    printf("%d\n",n);
    for(int i=1;i<=n;++i)puts("0");
    puts("1\n0 1");
    return 0;
}

Task4

我們說過ModifiedDijkstra在負權圖上可能被卡掉。

那麽怎麽建負權圖呢?

建立如下圖的結構就好了。

技術分享圖片

這樣的話每次ModifiedDijkstra會先沿邊權為0的邊更新偶數編號的節點,然後從小到大更新奇數編號的節點,然後從奇數編號的節點更新偶數編號的節點,又重新開始更新偶數編號的節點,更新結束以後才又開始更新下一個奇數編號的節點,時間復雜度變為指數級別。

具體建立多少個點,進行幾次詢問呢?

先假設我們盡可能建點,建立\(2x+1\)個點,那麽就需要建立\(3x\)條邊。

\(1+(2x+1)+(2\times 3x)+1+2=157\)

解釋一下:1是輸出\(V\)的數量,\(2x+1\)是輸出\(n_i\)的數量,\(3x\)是輸出邊的數量,由於要用兩個數字表示一條邊所以要乘2,\(1\)是輸出\(Q\)的數量,由於至少要有1個詢問,所以2是輸出詢問的數量。

解得\(x=19\),則\(V=39\),顯然不會把FloydWarshall卡掉。

但是我們發現這麽寫並不能讓ModifiedDijkstra T掉,但是已經很接近了。

我們發現當我們減少兩個點,建出的圖就會少2個點3條邊,也就少輸出了8個數,可以讓我們添加4次詢問。

於是我們令\(V=37,Q=5\),成功讓ModifiedDijkstra T掉。

code:

#include<bits/stdc++.h>
using namespace std;
int n=37,q=5,tt=1;
int main(){
    freopen("4.txt","w",stdout);
    printf("%d\n",n);
    puts("0");
    for(int i=1;i<n;++i)i&1?printf("1 %d %d\n",i-1,-tt<<1):(printf("2 %d 0 %d %d\n",i-2,i-1,tt),tt<<=1);
    printf("%d\n",q);
    for(int i=1;i<=q;++i)printf("%d 0\n",n-1);
    return 0;
}

Task5

類似Task2,建正權邊即可。

不過還沒完,這次的\(T\)要小一些。

不過如果像我這麽寫也很方便,直接把Task2的代碼中的\(n?\)改成300,\(T?\)改成1016即可。

code:

#include<bits/stdc++.h>
using namespace std;
int n=300,T=1016,Q=10,Num,RE,RN;
void print(int x){
    if(x!=n-1){
        printf("%d ",Num);
        for(int i=1;i<=Num;++i)printf("%d 1 ",x?x-1:n-1);
        puts("");
    }else{
        printf("%d ",Num+RE);
        for(int i=1;i<=Num+RE;++i)printf("%d 1 ",x?x-1:n-1);
        puts("");
    }
}
int main(){
    freopen("5.txt","w",stdout);
    printf("%d\n",n),T-=1+n+1+(Q<<1),Num=(T>>1)/n,RE=(T>>1)-Num*n;
    for(int i=0;i<n;++i)print(i);
    printf("%d\n",Q);
    while(Q--)printf("%d 0\n",n-1);
    return 0;
}

Task6

會了Task4的話這裏也很簡單了。

由於\(T\)減少了12,那麽我們可以減少4個點(減少了16個輸出的數字),相應地增加2個詢問(增加了4個輸出的數字)就好了。

並不用管OptimizedBellmanFord,絕對卡不掉的。

code:

#include<bits/stdc++.h>
using namespace std;
int n=33,q=7,tt=1;
int main(){
    freopen("4.txt","w",stdout);
    printf("%d\n",n);
    puts("0");
    for(int i=1;i<n;++i)i&1?printf("1 %d %d\n",i-1,-tt<<1):(printf("2 %d 0 %d %d\n",i-2,i-1,tt),tt<<=1);
    printf("%d\n",q);
    for(int i=1;i<=q;++i)printf("%d 0\n",n-1);
    return 0;
}

Task7

由於RecursiveBacktracking的時間復雜度與\(X\)的答案有很大關聯,\(X\)變大的話時間復雜度就會驟然上升,所以相當於我們要構建一個\(X\)很大的圖。

考慮構建完全圖。

\(2+V(V-1)=3004\),2是輸出\(V,E\)的數量,\(V\)個點的完全圖有\(\frac{V(V-1)}{2}\)條邊,需要用\(V(V-1)\)個數表示。

解得\(\lfloor V\rfloor=55\)

但是數據要求\(V>70\)

突然想到我們建完55個點的完全圖以後,不是還有多余的\(T\)嗎?

發現多余的\(T\)是32,可以建16條邊,而16條邊剛好將節點54到70連成一條鏈。

code:

#include<bits/stdc++.h>
using namespace std;
int n=71,gr=55,E=1501;
int main(){
    freopen("7.txt","w",stdout);
    printf("%d %d\n",n,E);
    for(int i=0;i<gr;++i)for(int j=i+1;j<gr;++j)printf("%d %d\n",i,j);
    for(int i=gr;i<n;++i)printf("%d %d\n",i-1,i);
    return 0;
}

Task8

同Task7,我們考慮建立\(X\)盡可能小的圖。

發現\(X\)最小的圖就是鏈,\(X=2\)

然而我們尷尬地發現\(V<1000,E>1500\),並不能建成鏈。

那怎麽辦?

我們令\(V=999\),先連成一條鏈,再將剩余的邊連在標號相差2的節點之間就好了。

這樣建出的圖\(X=3\)

code:

#include<bits/stdc++.h>
using namespace std;
int n=999,E=1501;
int main(){
    freopen("8.txt","w",stdout);
    printf("%d %d\n",n,E);
    for(int i=1;i<n;++i)printf("%d %d\n",i-1,i),--E;
    for(int i=0;i<n&&E;++i)printf("%d %d\n",i,i+2),--E;
    return 0;
}

[2019.3.21]洛谷P3640 [APIO2013]出題人