1. 程式人生 > 實用技巧 >最近做的幾道題

最近做的幾道題

理想路徑(Ideal Path, NEERC 2010, UVa1599)

給一個n個點m條邊\((2≤n≤100000,1≤m≤200000)\)的無向圖,每條邊上都塗有一種顏色。求從結點1到結點n的一條路徑,使得經過的邊數儘量少,在此前提下,經過邊的顏色序列的字典序最小。一對結點間可能有多條邊,一條邊可能連線兩個相同結點。輸入保證結點1可以達到結點n。顏色為\(1~10^9\)的整數。

輸入

包含多組輸入,每組輸入的第一行為n m,下面m行由a b c組成,ab代表圖中從a到b的一條無向邊,c代表這條邊的顏色。

輸出

對於每組輸入,找出滿足上面條件的一條最優路徑。輸出兩行,第一行是路徑長度,第二行是路徑。

Sample Input
4 6
1 2 1
1 3 2
3 4 3
2 3 1
2 4 4
3 1 1
2 2
1 1 1
1 2 2

Sample Output
2
1 3
1
2

思路

剛拿到沒啥思路,腦袋一片漿糊,看劉汝佳的思路之後有了一些想法,並寫出了程式碼,發現TLE,根據網上AC程式碼改了好久還是TLE,但是說個思路。

首先題目要求要滿足走的邊最少的前提下,再去滿足最小字典序。所以我們一定要先滿足走的路徑是最短的前提。

假如,我們在BFS的時候已經知道每個節點到終點最短還要走多遠的話,我們就只需要去在這些同距離的鄰接邊中選一個滿足最小字典序的邊走就好了。

假設下圖n=5

當前我們bfs操作所在的節點是1,假如我們現在已經知道從1到5的最少要走2條邊,那麼顯然我們只能從3和2裡選一個最小字典序,因為走4的話走的邊就不是最少的。

那麼既然得寫程式碼,選2和3捨棄4的操作是咋完成的呢?

假設d包含了每個節點到終點的最短路徑
在此圖中d[1]=2,d[2]=1,d[3]=1,d[4]=2,d[5]=0

let adjs = 節點1的所有鄰接邊
for(adj : adjs){
    if(d[adj]+1 == d[1]){
        選
    }else{
        不選
    }
}

因為最短路徑每一步都是最優選擇,所以一定和前一個節點是差1的關係,這個可以仔細思考下。

那麼萬事俱備,陣列d從哪來呢?上面做的所有操作都是基於d實現的。

我們可以反向bfs,也就是從終點到起點進行bfs,這樣就能記錄下d了。

程式碼

// PS:此程式碼不能AC,TLE。但是測了不少輸入都沒問題。
#include "iostream"
#include "cstdio"
#include "queue"
#include "cstring"
#include "string"

#define MAX_VERTEX 100001

using namespace std;
int n,m;
int a, b;
int c;
int s=0, t;
int vis[MAX_VERTEX];
int d[MAX_VERTEX];

struct GraphNode {
    GraphNode* next;
    int node_idx;
    int color;
}g[MAX_VERTEX];


void bfs_by_path(){
    memset(vis, 0, sizeof(vis));
    memset(d, 0, sizeof(d));
    queue<int> q;
    q.push(n);
    vis[n] = 1;
    d[n] = 0;
    while (!q.empty()) {
        int n_id = q.front(); q.pop();
        GraphNode u = g[n_id];
        GraphNode *cur = u.next;
        while (cur) {
            int v_id = cur->node_idx;
            if (!vis[v_id]) {
                d[v_id] = d[n_id] + 1;
                if(v_id==t){
                    return;
                }
                q.push(v_id);
                vis[v_id] = 1;
            }
            cur = cur->next;
        }
    }
}

void bfs_by_color(){
    printf("%d\n", d[1]); // 最短距離
    memset(vis, 0, sizeof(vis)); // 初始化
    vector<int> next{ 1 };
    vector<int> ans;
    for (int i = 0; i < d[1]; i++) { // 分層遍歷
        int minColor = 0x3fffffff;
        for (int uid : next) {
            GraphNode u = g[uid];
            GraphNode *cur = u.next;
            while (cur) {
                if (d[cur->node_idx]+1==d[uid]&&cur->color <= minColor) 
                    minColor = cur->color;
                cur = cur->next;
            }
        }
        ans.push_back(minColor); 
        vector<int> next_t;
        for (int uid : next) {
			GraphNode u = g[uid];
			GraphNode *cur = u.next;
            while (cur) {
                if (d[cur->node_idx] + 1 == d[uid] && cur->color == minColor) {
                    next_t.push_back(cur->node_idx);
                    vis[cur->node_idx] = 1;
                }
                cur = cur->next;
            }
        }
        next = next_t;
    }
    for (int i = 0; i < ans.size(); i++) { 
        cout << ans[i];
        if (i == ans.size() - 1) {
            cout << endl;
        }
        else {
            cout << " ";
        }
    }
}
void insert(int s,int d,int c){
    GraphNode *dptr = new GraphNode;
    dptr->color = c;
    dptr->node_idx = d;
    dptr->next = g[s].next;
    g[s].next = dptr;
}
int main() {


    while (scanf("%d %d", &n, &m) == 2) {
        fill(g, g + MAX_VERTEX, GraphNode());
		for (int i = 0; i < m; i++) {
			scanf("%d %d %d", &a, &b,&c);
            if (a == b)continue;
			insert(a, b, c);
			insert(b, a, c);
		}
		t = n;
		bfs_by_path();
		bfs_by_color();
    }
    return 0;
}

系統依賴(System Dependencies, ACM/ICPC World Finals 1997, UVa506)

軟體元件之間可能會有依賴關係,例如,TELNET和FTP都依賴於TCP/IP。你的任務是模擬安裝和解除安裝軟體元件的過程。首先是一些DEPEND指令,說明軟體之間的依賴關係(保證不存在迴圈依賴),然後是一些INSTALL、REMOVE和LIST指令,如表6-1所示。

在INSTALL指令中提到的元件稱為顯式安裝,這些元件必須用REMOVE指令顯式刪除。同樣地,被這些顯式安裝元件所直接或間接依賴的其他元件也不能在REMOVE指令中刪除。

輸入

包含多組輸入,每組輸入由若干行指令構成,以END指令結束。每行指令包含不超過80個字元,所有元件名稱都是大小寫敏感的。指令名稱均為大寫字母。

輸出

對於每行指令,echo它,就是原樣輸出指令。然後對於LIST,INSTALL和REMOVE,打印出他們的執行結果。

Sample Input
DEPEND   TELNET TCPIP NETCARD
DEPEND TCPIP NETCARD
DEPEND DNS TCPIP NETCARD
DEPEND  BROWSER   TCPIP  HTML
INSTALL NETCARD
INSTALL TELNET
INSTALL foo
REMOVE NETCARD
INSTALL BROWSER
INSTALL DNS
LIST
REMOVE TELNET
REMOVE NETCARD
REMOVE DNS
REMOVE NETCARD
INSTALL NETCARD
REMOVE TCPIP
REMOVE BROWSER
REMOVE TCPIP
END

Sample Output
DEPEND   TELNET TCPIP NETCARD
DEPEND TCPIP NETCARD
DEPEND DNS TCPIP NETCARD
DEPEND  BROWSER   TCPIP  HTML
INSTALL NETCARD
   Installing NETCARD
INSTALL TELNET
   Installing TCPIP
   Installing TELNET
INSTALL foo
   Installing foo
REMOVE NETCARD
   NETCARD is still needed.
INSTALL BROWSER
   Installing HTML
   Installing BROWSER
INSTALL DNS
   Installing DNS
LIST
   NETCARD
   TCPIP
   TELNET
   foo
   HTML
   BROWSER
   DNS
REMOVE TELNET
   Removing TELNET
REMOVE NETCARD
   NETCARD is still needed.
REMOVE DNS
   Removing DNS
REMOVE NETCARD
   NETCARD is still needed.
INSTALL NETCARD
   NETCARD is already installed.
REMOVE TCPIP
   TCPIP is still needed.
REMOVE BROWSER
   Removing BROWSER
   Removing TCPIP
   Removing HTML
REMOVE TCPIP
   TCPIP is not installed.
END

思路

一道模擬題,剛開始尋思挺簡單的。踩了幾個坑,依舊覺得沒多難。測試了很多示例都沒問題,丟到Vjudge上,直接RE。

主要就是要注意幾點

  1. 安裝一個軟體不僅要先安裝其依賴,還要遞迴安裝依賴的依賴
  2. 刪除軟體的時候,遞迴刪除依賴,但如果依賴是使用者手動安裝的,不是作為依賴安裝的,那就不能刪除
  3. 刪除依賴和軟體的時候,要注意這個軟體是不是還被其他軟體依賴著,如果是就不能刪除
  4. LIST的輸出順序,INSTALL和REMOVE的遞迴列印順序

首先,軟體包都是字串,不能直接使用,為了方便和快速,為每個軟體包設定一個獨立的ID。使用兩個陣列記錄依賴關係,d[x]記錄x依賴什麼軟體包,d2[x]記錄軟體包被誰依賴。這兩個陣列只記錄依賴關係,並不參與其他邏輯。

程式碼

// RE程式碼
#include "iostream"
#include "cstdio"
#include "map"
#include "string"
#include "sstream"
#include "vector"
#include <algorithm>
#include "set"

#define NOT_INSTALLED 0
#define EXPLICIT_INSTALLED 1
#define UNEXPLICIT_INSTALLED 2


using namespace std;

vector<vector<int> > d;
vector<vector<int> > d2;
vector<int> status;
map<string, int> name_id_map;
vector<string> name_cache;
vector<int> install_list;



int n2i(string name) {
    if (!name_id_map.count(name)) {
        int r = name_cache.size();
        d.push_back(vector<int>());
        d2.push_back(vector<int>());
        status.push_back(NOT_INSTALLED);
        name_cache.push_back(name);
        name_id_map[name] = r;
        return r;
    }
    return name_id_map[name];
}
void install(string name,int p_id,int type){
    if (status[p_id] != NOT_INSTALLED) {
        if(type == EXPLICIT_INSTALLED)
			cout << "   " << name << " is already installed." << endl;
        return;
    }
    status[p_id] = type;
    for (int dpd : d[p_id]) {
		install(name_cache[dpd], dpd,UNEXPLICIT_INSTALLED);
    }
    install_list.push_back(p_id);
    cout << "   Installing " << name << endl;
}


bool isNeeded(int p_id) {
    for (int q_id:d2[p_id]) {
        if (status[q_id] != NOT_INSTALLED)return true;
    }
    return false;
}
void remove(string name,int p_id,int user_input){
    if (status[p_id] == NOT_INSTALLED) {
        if(user_input)
			cout << "   " << name << " is not installed." << endl;
        return;
    }
    // 是否有程式依賴於此程式,如果無則直接移除,如果有,不能移除
    // 移除時需要判斷此程式是否是顯式安裝
    if (!user_input && status[p_id] == EXPLICIT_INSTALLED) return;
    if (!isNeeded(p_id)) {
        cout << "   Removing " << name << endl;
        status[p_id] = NOT_INSTALLED;
        install_list.erase(find(install_list.begin(), install_list.end(), p_id));
        for (int dpd: d[p_id]) {
			remove(name_cache[dpd], dpd,0);
        }
    }
    else {
        if(user_input)
			cout << "   " << name << " is still needed." << endl;
    }
}

int main() {
    int p_id;
    string line,cmd,op1,op2;
    while (getline(cin,line)) {
        cout << line << endl;
        if (line[0]=='E') {
            d.clear(); d2.clear(); status.clear();
            name_id_map.clear(); name_cache.clear(); install_list.clear();
            continue;
        }
        stringstream ss(line);
        cmd; ss >> cmd;
        if (cmd[0] == 'L') {
            for (int i:install_list) {
                cout << "   " << name_cache[i] << endl;
            }
        }else {
            ss >> op1;
			if (cmd[0]=='D') {
				while (ss >> op2) {
					d[n2i(op1)].push_back(n2i(op2));
					d2[n2i(op2)].push_back(n2i(op1));
				}
			}
			else if (cmd[0]=='I') {
				p_id = n2i(op1);
				install(op1, p_id, EXPLICIT_INSTALLED);
			}
			else if (cmd[0]=='R') {
				p_id = n2i(op1);
				remove(op1, p_id,1);
			}
        }
    }
    return 0;
}

UVa 673 Parentheses Balance

輸入一個包含“( )”和“[ ]”的括號序列,判斷是否合法。具體規則如下:

  • 空串合法。
  • 如果A和B都合法,則AB合法。
  • 如果A合法則(A)和[A]都合法。

輸入

第一行輸入n,下面n行輸入括號組合。

輸出

如果該行是合法的,輸出Yes否則輸出No

Sample Input
3
([])
(([()])))
([()[]()])()

Sample Output
Yes
No
Yes

思路

這題簡單,棧。就是程式碼有點醜,我也不想改了。

程式碼

// AC
#include "iostream"
#include "cstring"
#include "stack"
#include "cstdio"
//#include "cstdlib"

#define WRITE(a) printf(a)
//#define WRITE(a) fprintf(fp,a);
#define MAX 129

using namespace std;

char str[MAX];

//FILE* fp;
int main() {
    //fp = fopen("a.txt", "w");
    int n;
    scanf("%d", &n);
	getchar();
    for (int i = 0; i < n; i++) {
        cin.getline(str, MAX);
        int j;
		stack<char> stk;
		int is_vaild = 1;
        for (j = 0; str[j]; j++) {
            if (str[j] == '(' || str[j] == '[') {
                stk.push(str[j]);
                continue;
            }
            if ((str[j] == ')' || str[j] == ']') && stk.empty()) {
                is_vaild = 0;
                break;
            }
            if (str[j] == ')' && stk.top() != '(') {
                is_vaild = 0;
                break;
            }
            if (str[j] == ']' && stk.top() != '[') {
                is_vaild = 0;
                break;
            }
            stk.pop();
        }
        // 如果讀取到尾了並且stk為空
        if (!str[j] && stk.empty())
            is_vaild = 1;
        else is_vaild = 0;
        if (is_vaild)
            WRITE("Yes");
        else
            WRITE("No");
        WRITE("\n");
    }
    return 0;
}

UVa 712 S-Trees

給出一棵滿二叉樹,每一層代表一個01變數,取0時往左走,取1時往右走。

給出所有葉子的值以及一些查詢,求每個查詢到達的葉子的值。例如,有4個查詢:000、010、111、110,則輸出應為0011。

輸入

包含多組輸入,每組輸入由n開始,接下來一行是n個節點。代表二叉樹的每一層。然後一行是葉子節點的編號。然後是m,下面m行是VVS,就是查詢,每次查詢你需要從根節點開始,0往左,1往右。走了n次後,到達葉子節點,輸出你到達的葉子節點的值。

注意:VVS中的第i步和xi的順序一致,如果不考慮這個會走錯

假如x2在根節點,那麼對於輸入010你應該先往右走,就是找VVS中的第二步,而第二步是1。

輸出

對於每個示例,輸出第一行S-Tree #j:,j是當前示例編號,然後下一行是m個VVS走到的葉子節點的順序輸出。

Sample Input
3
x1  x2  x3
00000111
4
000
010
111
110
3
x3  x1  x2
00010011
4
000
010
111
110
0

Sample Output
S-Tree  #1:
0011

S-Tree  #2:
0011

思路

也不難,可以直接模擬二叉樹。但是沒必要。

想想滿二叉樹如果按層序排列的話是什麼樣的?

這樣的:

每一層都按順序排列。對於當前節點i,i*2就是左子,i*2+1就是右子。

那麼對於題目中最後的一排葉子,一定是2^n個,並且下標是從2^n開始。我們使用一個2^n的陣列就能記錄最後一排葉子。

然後我們假設n=3,對於一組VVS,比如010,當前xi順序為x1 x2 x3也就是不用考慮順序,我們從根節點開始走這個VVS。

當前節點根節點 1 
取VVS第一個 0 
走到左子
當前節點1*2 = 3
取VVS第二個 1
走到右子
當前節點3*2+1=6
取VVS第三個 0
走到左子
當前節點6*2=12

因為最後一排下標從`2^n`開始,所以12-2^3 = 4
那它就會去記錄最後一排葉子的陣列中找第4個元素
對於此次VVS,走到了第4個葉子元素上

需要注意的點

  • 順序!!順序!!VVS的順序!!
  • 上面的下標很理想化,但是程式碼中會有些減一的操作,別忘了,或者定義的時候就從1開始,0不用。
  • 注意換行符用getchar取走

程式碼

// AC
#include "iostream"
#include "cstdio"
#include "string"
#include "cmath"
#include "vector"
#define MAX 128
#define MAXN 7

using namespace std;

int t[MAX];

int left(int i) {
    return i * 2;
}
int right(int i) {
    return i * 2 + 1;
}
// 獲取一位
int get_1bit(){
	char c;
	scanf("%c", &c);
    return c == '0' ? 0 : 1;
}
int main() {
    int n,m,c=0;
    string tmp;
    while (scanf("%d", &n) != EOF && n!=0) {
        c++; // Case Number
        getchar();
        int order[MAXN]; // 因為順序是按照xi的排列來的,所以記錄下順序
        for (int i = 0; i < n; i++) {
            cin >> tmp;
            order[i] = stoi(tmp.substr(1))-1;
        }
        getchar();


        // 記錄最底層的終端節點
        int t_num = pow(2, n);
        for (int i = 0; i < t_num; i++) 
            t[i] = get_1bit();

        scanf("%d", &m);
        vector<int> ans; // 答案
        int vvs[MAXN]; // vvs
        // 這裡應該按order中的順序遍歷 
        for (int i = 0; i < m; i++) {
            getchar();
			int s = 1;
            for (int j = 0; j < n; j++)
                vvs[j] = get_1bit();

            for (int j = 0; j < n; j++) {
				s = vvs[order[j]] ? right(s) : left(s);
            }
            ans.push_back(t[s - t_num]);
        }
        printf("S-Tree #%d:\n", c);
        for (int i : ans) {
            printf("%d", i);
        }
        printf("\n\n");
    }
    return 0;
}

參考