題目
點這裏看題面,密碼:6wfu
點這裏看出題人題解,密碼:5uhh
喪心病狂的你想看我們的分數的話就戳這裏
Johann是蒟蒻,Orz各位神犇
T1 NUMBER
1、 因爲 4*7+3=8*3+7,所以這題目給出的兩種操作是不受先後順序影響的。
2、 (4x + 3)
3、如果你像我一樣聰(NaoDong)明(Da),你會發現4x+3=(2x+1)
4、 題目保證答案不超過 100000。
所以枚舉(2x+1)的次數,優先轉化成(8x+7)就可以了
如果沒有發現3,也可以優先枚舉(8x+7),稍微繁瑣一些
#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define red(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
const ll mod = 1000000007ll;
ll n;
int main() {
freopen("number.in", "r", stdin);
freopen("number.out", "w", stdout);
scanf("%I64d", &n);
n %= mod;
int cnt = 0;
while(n != 0) {
n = (n << 1ll) + 1ll; n %= mod;
cnt++;
}
if (cnt % 3 == 0) cnt /= 3;
else if (cnt % 3 == 1) cnt = (cnt - 4) / 3 + 2;
else cnt = cnt / 3 + 1;
printf("%d\n", cnt);
return 0;
}
T2 TRAVEL
一開始GJ大神說要Tarjan,我受到了驚嚇
先用Floyd把新圖建出來,對每一條邊打好標記
然後spfa,用d[u][k]記錄狀態,u表示當前節點,k表示“路徑2”走了k次
統計答案
#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define red(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
const int inf = 1000000000;
const int maxn = 200, maxm = 20000;
struct edge{
int from, to, len, tag, nxt;
}e[maxm];
struct node{
int pos, step;
};
int G[maxn][maxn], g[maxn][maxn], d[maxn][20], inq[maxn][20];
int head[maxn];
int n, m, lim, tail = 0;
inline void setIO() {
freopen("travel.in", "r", stdin);
freopen("travel.out", "w", stdout);
}
void init() {
scanf("%d%d%d", &n, &m, &lim);
rep(i, 1, n)
rep(j, 1, n)
g[i][j] = i == j ? 0 : inf;
memset(e, 0, sizeof(e));
rep(i, 1, n) head[i] = -1;
rep(i, 1, m) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
g[x][y] = min(g[x][y], z);
G[x][y] = g[x][y];
}
}
void make_graph() {
rep(k, 1, n) {
rep(i, 1, n) {
rep(j, 1, n) {
if (g[i][k] + g[k][j] < g[i][j])
g[i][j] = g[i][k] + g[k][j];
}
}
}
rep(i, 1, n) {
rep(j, 1, n) {
if (i == j) continue;
if (G[i][j] == 0) continue;
if (g[i][j] != inf && g[j][i] != inf) {
e[++tail].from = i;
e[tail].to = j;
e[tail].len = G[i][j];
e[tail].tag = 0;
e[tail].nxt = head[i];
head[i] = tail;
}else {
e[++tail].from = i;
e[tail].to = j;
e[tail].len = G[i][j] * 2;
e[tail].tag = 1;
e[tail].nxt = head[i];
head[i] = tail;
}
}
}
}
void spfa(int s, int t) {
rep(i, 1, n)
rep(j, 0, lim)
d[i][j] = inf;
d[s][0] = 0;
rep(i, 1, lim) d[s][i] = inf;
memset(inq, 0, sizeof(inq));
queue<node> q;
q.push((node){s, 0});
inq[s][0] = 1;
while(!q.empty()) {
node point = q.front();
int now = point.pos, k = point.step;
q.pop();
inq[now][k] = 0;
for(int i = head[now]; i != -1; i = e[i].nxt) {
int v = e[i].to;
if (d[now][k] + e[i].len < d[v][k + e[i].tag] && k + e[i].tag <= lim) {
d[v][k + e[i].tag] = d[now][k] + e[i].len;
if (!inq[v][k + e[i].tag]) {
inq[v][k + e[i].tag] = 1;
q.push((node){v, k + e[i].tag});
}
}
}
}
}
void print() {
int ans = inf;
rep(i, 0, lim) ans = min(ans, d[n][i]);
if (ans == inf) printf("-1\n");
else printf("%d\n", ans);
}
int main() {
setIO();
init();
make_graph();
spfa(1, n);
print();
return 0;
}
T3 BRICK
做法和STD有一些不同,先說我的
先想出%60的dp,用f[i][j][l][r]記錄狀態(第i行取l到r,空j格),在枚舉ll和rr,從i-1行的ll到rr轉移過來,
由於轉移是迭代的,所以狀態可以省去第一維,但沒有優化時間
考慮優化轉移。ll和rr同時滿足 ll <= r 且 rr >= l, 這個條件即表示ll爲行,rr爲列的矩陣中,(r,l)這個點右上方所有值,而轉移就是要求這些值中的最大值。不難發現,這個“矩陣”就是第i-1行的決策表,需要注意的是,這個決策表對每一個j(空j格)都要記錄決策表g
這樣就只需要兩個
#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define red(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long lont
int n, m, k;
int h[100][100][100], g[100][100][100];
int sum[100][100], a[100][100];
int main() {
freopen("brick.in", "r", stdin);
freopen("brick.out", "w", stdout);
scanf("%d%d%d", &n, &m, &k);
k = n * m - k;
rep(i, 1, n)
rep(j, 1, m)
scanf("%d", &a[i][j]);
rep(i, 1, n) {
sum[i][0] = 0;
rep(j, 1, m) sum[i][j] = sum[i][j - 1] + a[i][j];
}
memset(g, 0, sizeof(g));// maxnum of a matrix
memset(h, 0, sizeof(h));// ans that is being calculated
rep(i, 1, n) {
rep(j, 1, k) {
rep(l0, 1, m) {
rep(r0, l0, m) {
int len = r0 - l0 + 1;
if (len > j) break;
h[j][l0][r0] = g[j - len][r0][l0] + sum[i][r0] - sum[i][l0 - 1];
}
}
}
rep(j, 1, k) {
rep(l, 1, m) {
red(r, m, 1) {
g[j][l][r] = h[j][l][r];
g[j][l][r] = max(g[j][l][r], g[j][l][r + 1]);
g[j][l][r] = max(g[j][l][r], g[j][l - 1][r]);
}
}
}
}
int ans = 0;
rep(l0, 1, m)
rep(r0, l0, m)
ans = max(ans, h[k][l0][r0]);
printf("%d\n", ans);
return 0;
}
題解提供了一種不同的做法,在這裏也貼出
1、 由於max(nm − 64,0) ≤ k < nm,所以保留的格子數不會超過 64 個。
2、 設 left[i][j][k]表示從(i,j)格子開始,只允許向左走或者向上走,總共走k步可以得到的
最優值,設 right[i][j][k]表示從(i,j)格子開始,只允許向右走或者向上走,總共走k步
可以得到的最優值。這樣表示了狀態後,問題的答案可以通過枚舉底行遍歷的起始
位置 x 列,然後取 left[n][x][nm-k]和 right[n][x][nm-k]的最大值即可。
3、 關於轉移,對於 left[i][j][k]狀態有兩類轉移方式:第一種情況,是在當前行向左走一
步,狀態轉移到左邊相鄰的格子。第二種情況,left 狀態如果不向左擴展,就只能向
上擴展。對於最優解來自向上擴展的狀態,根據連續性的特點,我們可以發現這類
狀態有這樣一個特點:或者(i,j)是方案中第 i 行的一個端點(左端點獲右端點),或者
它向上一行到達的格子(i-1,j)是對應行的一個端點。
具體轉移要分三種情況:
(1),向上走一行到達的格子(i-1,j)是對應行的左端點;此時向上擴展後不能再向左擴
展,該情況如圖(a)所示,黑色格子爲當前位置。由於(i,j)不一定是當前的左端點,因
此需要枚舉當前位置左邊還需要取的一段格子的長度 x,對應圖(a)中的灰色段,然
後向 right[i-1][j][k-x-1]轉移。
(2),向上走一行到達的格子(i-1,j)是對應行的右端點;此時向上擴展後不能再向右擴
展,該情況如圖(b)所示,黑色格子爲當前位置。由於(i,j)不一定是當前的左端點,因
此需要枚舉當前位置左邊還需要取的一段格子的長度 x,對應圖(b)中灰色段,然後
向 left[i-1][j][k-x-1]轉移。
(3),當前位置是當前行的一個左端點;此時,向上擴展會有左右兩個方向可以選擇,
如圖(c)和(d)所示。與前面兩種情況相似,由於上升一行後不是端點位置,在左右某
個方向轉移的同時,需要枚舉另一個方向需要取的格子的長度,對應圖(c)和(d)中的
灰色段。
狀態 right[i][j][k]的轉移方法跟 left[i][j][k]的轉移方法類似,這裏不再闡述。根據前面分析的
狀態轉移方法,向左或向右轉移的時間複雜度爲 O(1),而向上轉移的時候,需要枚舉某個灰
色段的長度,這個計算量爲 O(N)。所以該算法總的時間複雜度爲O(NMK 2 ),這裏的 K 表示要
保留的格子個數K ≤ 64。
#include <iostream>
#include <cstdio>
#define maxn 65
#define maxlongint 2147483647
using namespace std;
int n,m,kth,ans;
int a[maxn][maxn],sum[maxn][maxn],Left[maxn][maxn][maxn],Right[maxn][maxn][maxn];
void init()
{
cin>>n>>m>>kth;
kth=n*m-kth;
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
{
cin>>a[i][j];
sum[i][j]=sum[i][j-1]+a[i][j];
}
}
void slove()
{
for (int k=1; k<=kth; k++)
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
{
int &p=Left[k][i][j];
int &q=Right[k][i][j];
int tmp;
if (k==1)
{
p=q=a[i][j];
continue;
}
p=q=-maxlongint;
if (j>1) p=Left[k-1][i][j-1]+a[i][j];
if (j<m) q=Right[k-1][i][j+1]+a[i][j];
if (i==1) continue;
for (int x=0; x<k; x++)
{
int remain=k-x-1;
if (x<j) p=max(p,sum[i][j]-sum[i][j-1-x]+max(Right[remain][i-1][j],Left[remain][i-1][j]));
if (x+j<=m) q=max(q,sum[i][j+x]-sum[i][j-1]+max(Right[remain][i-1][j],Left[remain][i-1][j]));
if (x<j && x<k-1)
{
tmp=Right[remain][i-1][j]+a[i][j]+sum[i-1][j-1]-sum[i-1][j-1-x];
p=max(p,tmp);
q=max(q,tmp);
}
if (x+j<=m && x<k-1)
{
tmp=Left[remain][i-1][j]+a[i][j]+sum[i-1][j+x]-sum[i-1][j];
p=max(p,tmp);
q=max(q,tmp);
}
}
}
ans=0;
for (int i=1; i<=m; i++) ans=max(ans,max(Left[kth][n][i],Right[kth][n][i]));
cout<<ans<<endl;
}
int main()
{
freopen("brick.in","r",stdin);
freopen("brick.out","w",stdout);
init();
slove();
}
孰優孰劣,仁者見仁智者見智
T4 MAXIMIZE
1、 二分圖匹配的模型是容易想到的。構造一個二分圖 G(X,Y),X 集中的每個頂點對應原
圖的每個頂點,Y 集中的每個頂點對應原圖中每一條邊。若原圖存在一條邊可以賦
值給某個頂點,那麼在圖 G 中相應頂點中連邊,邊權是原圖中的那條邊的權值。但
對於這個題來說,這樣顯然時間複雜度過高。
2、 原圖中所有邊的權值最大者,一定存在於最優分配方案中,否則我們可以把它分配
給一個頂點,使得解更優。對於原圖中權值第二大的邊,如果在不影響權值最大的
邊的分配,則這條邊也存在於最優解中。以此類推,我們按照權值從大到小排序原
圖的邊,然後以此判斷每條邊的加入是否影響比它大的邊的分配,如果不影響,則
選取,否則棄之。
現在的問題是怎麼判斷一條邊的加入是否影響比它大的邊的分配。
1、 考慮一個聯通塊,對於本題,在談論混合圖的連通性時,只考慮它的無向邊,而忽
略它的有向邊。
2、 如果滿足上述條件的分配方案是存在的,那麼對於圖上的每個連通塊,頂點的數量
一定不少於邊的數量。
3、 其實,相反,若圖上每個連通塊的頂點數量都不少於邊的數量,那麼是否存在一個
滿足上述條件的方案?答案是肯定的。若在一個連通塊上,有一條邊未被分配,那
麼一定有另外一個頂點未分配權值。既然圖是連通的,那麼這條邊和頂點之間就有
一條路徑。可以沿着這條路徑修改分配方案,使得這條邊被分配而不影響其餘分配
的邊。所以最後問題轉化爲統計一個連通塊的頂點數和邊數之差的問題。
4、 這個問題可以用並查集解決
#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define red(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
const int maxn = 1500, maxm = maxn * (maxn - 1) / 2;
struct node{
int from, to, tag, len;
}e[maxm];
int fa[maxn], delta[maxn];
int n, m, ans = 0;
bool cmp(node a, node b) { return a.len > b.len; }
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int main() {
freopen("maximize.in", "r", stdin);
freopen("maximize.out", "w", stdout);
scanf("%d%d", &n, &m);
rep(i, 1, m) scanf("%d%d%d%d", &e[i].from, &e[i].to, &e[i].tag, &e[i].len);
sort(e + 1, e + m + 1, cmp);
rep(i, 1, n) fa[i] = i, delta[i] = 1;
rep(i, 1, m) {
if (e[i].tag == 0) {
int x = find(e[i].from);
int y = find(e[i].to);
if (x != y) {
if (delta[x] + delta[y] > 0) {
ans += e[i].len;
delta[x] += delta[y] - 1;
fa[y] = x;
}
} else {
if (delta[x] > 0) {
ans += e[i].len;
delta[x]--;
}
}
} else{
int x = find(e[i].from);
if (delta[x] > 0) {
ans += e[i].len;
delta[x]--;
}
}
}
printf("%d\n", ans);
return 0;
}
尾聲
的確好久沒有做題了,非常愧疚呢~
這套題前三題都是做出來的,最後一題來不及想,寫了暴力。結束之後也做出來了,感覺題目出的還是挺合理,四道題有點不可理喻TAT
數據出的簡直goushi,在testdata裏面“打草稿”!
其實這是一套陳題,2012年的時候的訓練題-_-||
GJ和QY神犇都做過的哇咔咔……難怪做的那麼快的說
好像沒有什麼可以吐槽的了
哦對,還有SXP同學的四個妹子