【解題報告】2015ACM/ICPC亞洲區瀋陽站
B. Bazinga(HDU5510)
思路
設第i個字串儲存在ss[i][]中。本題最直觀最樸素的做法是列舉兩個字串ss[i]和ss[j]
我的做法是,先令所有的ss[i]
例如ss[1] = “ab”, ss[2] = “bc”, ss[3] = “abc”。在ss[1],ss[2]同ss[3]的比較中發現,ss[1]和ss[2]都是ss[3]的子串。對應的位置資訊為
程式碼:
#include <cstdio>
#include <cstring>
const int maxn = 5e2 + 5, maxLen = 2e3 + 5;
char ss[maxn][maxLen];
int t, n, ans, next[maxLen], a[maxn][2];
void getNext(char* p) {
int lp = strlen(p);
next[0] = -1;
for(int j=0, k=-1; j < lp-1;) {
if(k == -1 || p[j] == p[k]) {
j++, k++;
if(p[j] != p[k]) next[j] = k;
else next[j] = next[k];
}
else k=next[k];
}
}
int kmp(char* s, char* p) {
int i = 0, j = 0;
int ls = strlen(s), lp = strlen(p);
for(; i < ls && j < lp;) {
if(j == -1 || s[i] == p[j]) i++, j++;
else j = next[j];
}
if(j == lp) return i - j;
else return -1;
}
int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%s", ss[i]);
}
int len = strlen(ss[n]);
bool exist = false;
// 判斷n是否是答案
for(int i = 1; i < n; i++) {
getNext(ss[i]);
int res = kmp(ss[n], ss[i]);
if(res == -1) {
exist = true;
break;
}
// 記錄位置資訊
a[i][0] = res;
a[i][1] = res + strlen(ss[i]) - 1;
}
if(exist) {
printf("Case #%d: %d\n", kase, n);
continue;
}
ans = -1;
// 列舉兩個字串,比較它們的位置資訊
for(int i = n - 1; i > 0; i--) {
for(int j = 1; j < i; j++) {
if(a[i][0] > a[j][0] || a[i][1] < a[j][1]) {
ans = i;
i = 0;
break;
}
}
}
printf("Case #%d: %d\n", kase, ans);
}
return 0;
}
D. Pagodas(HDU 5512)
思路
根據貝祖定理(擴充套件歐幾里德演算法的理論基礎),對於任意兩個整數
程式碼
#include <bits/stdc++.h>
using namespace std;
int t, n, a, b, g, ub, times;
int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
printf("Case #%d: ", kase);
scanf("%d%d%d", &n, &a, &b);
g = __gcd(a, b);
ub = n - n % g;
times = ub / g - 2;
puts(times % 2 ? "Yuwgna" : "Iaka");
}
return 0;
}
F. Frogs(HDU 5514)
思路(容斥原理)
首先,根據貝祖定理可知,第i只青蛙能夠到達的石塊的編號組成的數列應是以起點編號0為首項,以
那麼這是不是意味著我們可以列舉每隻青蛙,然後對每隻青蛙對應的等差數列求和,然後再將所有的數列和相加就能得到正確答案了呢?這種思路是不對的,因為這樣簡單地相加是會有重複的。例如
所以,接下來的問題是要解決重複。對以樣例第一組為例,最直觀的辦法是額外減去
對第一隻青蛙我們只需要在最後的結果ans變數中加上第一隻青蛙對應的編號數列和即可。然後考慮第二隻青蛙,除了加上數列和以外,還要減少一隻虛擬青蛙
其實,這樣重複加需要減來抵消,重複減又需要加來抵消的情況符合容斥原理的模型(想象求幾何圖形交的總面積)。實現方法是:記
這個過程像極了求幾何圖形交的總面積的過程,因此我才將表示某部分是否已經算入ans的標記陣列的名字命名為painted。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 10, maxFac = 1e3;
int t, n, m, a, g[maxn], painted[maxFac];
vector <int> fac;
ll ans;
ll sum(ll n, ll d) {
return n * (n-1) / 2 * d;
}
int main() {
scanf("%d", &t);
for(int kase = 1; kase <= t; kase++) {
scanf("%d%d", &n, &m);
// 輸入資料的同時預處理出g
for(int i = 0; i < n; i++) {
scanf("%d", &a);
g[i] = __gcd(a, m);
}
fac.clear();
// 列舉m的因數
for(int i = 1; i * i <= m; i++) {
if(m % i == 0) {
fac.push_back(i);
if(i * i != m) {
fac.push_back(m / i);
}
}
}
sort(fac.begin(), fac.end());
memset(painted, 0, sizeof(painted));
for(int i = 0; i < n; i++) {
for(int j = 0; j < fac.size(); j++) {
// 初始化painted陣列
if(fac[j] % g[i] == 0) {
painted[j] = 1;
}
}
}
ans = 0;
for(int i = 0; i < fac.size(); i++) {
if(painted[i] != 0) {
// 將數列和加到最後結果中
ans += painted[i] * sum(m / fac[i], fac[i]);
// 維護painted陣列
for(int j = i + 1; j < fac.size(); j++) {
if(fac[j] % fac[i] == 0) {
painted[j] -= painted[i];
}
}
}
}
printf("Case #%d: %I64d\n", kase, ans);
}
return 0;
}
M.Meeting(HDU5521)
思路
分別以兩隻牛的起點為起點求兩個最短路陣列,然後列舉每個點就能找到最好的那個點。注意,如果確定要在某個點相會,一隻牛先到了的話需要等另外一隻牛。另外,圖的每個完全子圖都要建邊的話複雜度將難以讓人忍受所以對於每個完全子圖,設定一個虛擬的結點,再從虛擬結點向子圖中的所有結點建邊,這樣建邊的複雜度就降低到了線性複雜度。
程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair <ll, int> p;
const int maxn = 1e5 + 5, maxm = 1e6 + 5;
const ll INF = 1e15 + 5;
bool vis[maxn + maxm];
int t, m, n, si, ti;
ll ans, maxTime[maxn];
vector <int> vec;
// 邊的結構體
struct edge {
int to, dist;
edge() {}
edge(int to, int dist): to(to), dist(dist) {}
};
// dijkstra演算法模板
struct dijkstra {
ll d[2][maxn+maxm];
vector <edge> G[maxn+maxm];
void init() {
for(int