1. 程式人生 > >qbxt國慶水題記day3

qbxt國慶水題記day3

qbxt國慶水題記

day3

//30 + 5 + 30 = 65
//暴力 + 暴力+ 暴力

tree

1.1 題目
從前有一棵樹,編號 1 到 n,確定一個根節點,最大化所有點深度之和

1.2 輸入Ṭ
第一行 n 接下來 n - 1 行表示樹的每條邊

1.3 輸出Ṭ
一個整數,表示根節點編號。如果有多個節點滿足條件,輸出編號最小的

1.4 Sample Input
8
1 4
5 6
4 5
6 7
6 8
2 4
3 4

1.5 Sample Output
7

1.6 資料範圍及約定
對於 30% 的資料滿足:1 ≤n ≤1000
對於另外 20% 的資料滿足:樹是一條鏈
對於 100% 的資料滿足,1 ≤n ≤106
讀入量過大,最好加上讀入優化

樹形DP
用dis[]表示每個節點的子樹和
用size[]表示每個節點有多少個子節點
爆搜出dis和size
對於每個節點深度和為 vis[節點] = vis[父親] + (n - size[節點])- size[節點];

程式碼(爆棧了不知到對不對~~不想寫bfs)

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

const int maxn = 1000000 + 100;
int n;
struct edge {
    int u,v;
    int
next; }e[maxn]; int head[maxn], tot = 0; void add(int u, int v) { e[++tot] = (edge){u,v,head[u]}; head[u] = tot; } int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0'
&& ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } int size[maxn],dis[maxn]; void dfs1(int x, int fa) { for(int i = head[x]; i; i = e[i].next) { int v = e[i].v; if(v != fa){ dfs1(v,x); dis[x] += dis[v] + size[v]; size[x] += size[v]; } } size[x]++; } int vis[maxn]; void dfs2(int x, int fa) { if(x == 1) vis[x] = dis[x]; else vis[x] = vis[fa] + n - 2 * size[x]; for(int i = head[x]; i; i = e[i].next) { int v = e[i].v; if(v != fa) { dfs2(v,x); } } } int main() { freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); n = read(); for(int i = 1; i < n; i++) { int u = read(), v = read(); add(u,v); add(v,u); } dfs1(1,0); dfs2(1,0); int ans = 0,l = 0; for(int i = 1; i <= n; i++) { if(ans < vis[i]) { l = i; ans = vis[i]; } } cout<<l<<endl; return 0; }

程式碼
std用的拓撲(表示不好理解)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define rep(i,l,r) for (int i=l; i<=r; i++)
#define drep(i,r,l) for (int i=r; i>=l; i--)
#define ll long long
#define pb push_bake
const int N = 1000008; 
int n, tot, head[N], h[N], fa[N], sum[N];
bool vis[N];
ll Down[N], Up[N]; 
int read()
{
    char c; int w = 1, num = 0;
    for (c = getchar(); !isdigit(c) && c != '+' && c != '-'; c = getchar());
    if (c == '-') c = getchar(), w = -1; 
    if (c == '+') c = getchar();
    for (; isdigit(c); c = getchar()) num = num * 10 + c - '0';
    return num * w; 
}
struct Node
{
    int next, node; 
}e[2 * N];
inline void add(int x, int y)
{
    e[++tot].next = head[x], head[x] = tot, e[tot].node = y;
    e[++tot].next = head[y], head[y] = tot, e[tot].node = x;
}
void topsort()
{
    int l = 1, r = 1;
    h[1] = 1; vis[1] = 1;
    while (l <= r)
    {
        int u = h[l++];
        for (int i = head[u], v; v = e[i].node, i; i = e[i].next)
        if (!vis[v])
        {
            vis[v] = 1;
            fa[v] = u;
            h[++r] = v;
        }
    }
}
int main()
{
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    n = read();
    rep(i, 1, n - 1)
    {
        int u, v;
        u = read(); v = read();
        add(u, v);
    }  
    topsort();
    drep(k, n, 1)
    {
        int u = h[k];
        sum[u] = 1;
        for (int i = head[u], v; v = e[i].node, i; i = e[i].next)
        if (v != fa[u])
        {
            sum[u] += sum[v];
            Down[u] += Down[v] + sum[v];
        }
    }
    rep(k, 2, n)
    {
        int u = h[k];
        Up[u] += Up[fa[u]] + sum[1] - sum[fa[u]] + 1;
        Up[u] += Down[fa[u]] - Down[u] - sum[u] + sum[fa[u]] - sum[u] - 1; 
    }
    ll Max = 0; int ans;
    rep(i, 1, n) if (Up[i] + Down[i] > Max) Max = Up[i] + Down[i], ans = i;
    printf("%d\n", ans);
    fclose(stdin); fclose(stdout);
    return 0;
}

safe

2.1 題目
“秋名山上行人稀, 常有車神較高低。如今車道依舊在, 不見當年老司機”
從前有一位車神,要行駛秋名山上的一條公路。沿著公路一次站著 n 個觀眾,第 i 個觀眾
對他的喜歡程度為 a[i]。如果 a[i]<0,說明這個觀眾討厭他,有可能在他經過時搞一些危險的
事情,危及他的安全。
於是,車神想選出兩段路,這兩段路對應的兩段觀眾的喜歡值之和最大。這樣的話,車神
在這兩段路行駛就最為安全,可以適當降低警惕。
你的任務就是計算選出的這兩段觀眾喜歡值之和的最大值。

2.2 輸入Ṭ
第一行:N,表示有 N 個人站成一排觀看比賽
第二行:有 N 個數字,表示每個人的喜歡值

2.3 輸出Ṭ
輸出選出的這兩段觀眾喜歡值之和的最大值。注意一定要選出兩段觀眾,每段觀眾至少一
人。

2.4 樣ֻ輸入
7
4 -5 3 -1 11 -2 -1

2.5 樣ֻ輸出
17

2.6 樣例解釋
第一段是第一個人,第二段是第三到第五個人

2.7 資料範圍及㓖定
30% 的資料,保證 n<=100;
100% 的資料,保證 n<=65535,DI 值的絕對值小於 127;
答案在 C++ 的 long long 範圍內“`

//dp呵呵~~~~~~~~~~~~~~~~~~~不會
最長子段和的變形
我們用陣列 f[i] 表示以 i 結尾的最大子段和,g[i] 表示以 i 開頭的最大子段和。
我們可以O(n)dp 預處理出這兩個陣列。
std中用的是ST表(表示資料結構渣不會)
但有方法O(n)解決
f[i] 表示 i 以前的最大子段和,g[i] 表示 i 以後的最大子段和。
f[i] = max(f[i],f[i-1]),g[i] = max(g[i],g[i+1])
列舉i即可

程式碼

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define ll long long

const int maxn = 1000000 + 100;
ll n,a[maxn];
ll g[maxn],f[maxn];


ll read() {
    ll x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9') {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

int main() {
    freopen("safe.in","r",stdin);
    freopen("safe.out","w",stdout);
    n = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    f[1] = a[1],g[n] = a[n];
    for(int i = 2; i <= n; i++) f[i] = max(a[i],f[i-1] + a[i]);
    for(int i = n - 1; i >= 1; i--) g[i] = max(a[i],g[i+1] + a[i]);
    for(int i = 2; i <= n; i++) f[i] = max(f[i],f[i-1]);
    for(int i = n - 1; i >= 1; i--) g[i] = max(g[i],g[i+1]);
    ll ans = -99999999999L;
    for(int i = 2; i <= n; i++) {
        ans = max(ans, f[i-1] + g[i]);
    }
    cout<<ans<<endl;
    return 0;
}

還有奇葩的做法
ans = r2 - l2 + r1 - l1
列舉i 求出最大的 -l2 再求 r2 - l2 …… 到 r2 - l2 + r1 - l1
程式碼

#include <algorithm>
#include <cstdio>
typedef long long LL;
const LL INF = 1000000000000000LL;
const int N = 70000;
using std::max;
int main() {
  freopen("safe.in", "r", stdin);
  freopen("safe.out", "w", stdout);
  int n, x;
  LL a = 0, b = -INF, c = -INF, d = -INF, S = 0;
  scanf("%d", &n);
  for (int i = 1; i <= n; ++i) {
    scanf("%d", &x);
    S += x;
    d = max(d, c + S);
    b = max(b, a + S);
    c = max(c, b - S);
    a = max(a, -S);
  }
  printf("%I64d\n", d);
  return 0;
}

shoot

3.1 題目
從前有 n 個人,愉快地生活在一起。直到有一天他們學會了互黑。
每個人都有且僅有一個黑的目標 (這個目標有可能是他自己,這叫自黑)。對於那些沒人黑
的人 (其他所有人黑的目標都不是他),他們想刷存在感,於是可以選擇放棄黑自己的目標而選
擇自黑 (也就是二選一)。
注意只有一開始沒有作為任何人的目標的人才可以放棄目標選擇自黑。
規定輪到某個沒有被黑的人時,必須且僅能黑一次。所有被黑過的人都不能再黑別人了。
當然一個人可以被黑很多次。
由你來決定一個順序,大家按照順序行動。求問最後被黑的人數的最小值和最大值。

3.2 輸入Ṭ
第一行一個整數 n
第二行 n 個整數,第 i 個整數 ai 表示第 i 個人的目標,滿足 1 ≤ai ≤n

3.3 輸出Ṭ
兩個整數,依次表示最小值和最大值

3.4 樣ֻ輸入
8
2 3 2 2 6 7 8 5

3.5 樣ֻ輸出
3 6

3.6 樣ֻ例解釋
最小值:按照 1,4,2,3,5,6,7,8 的順序行動。輪到 2,6,8 時它們已經被黑所以他們不能再
黑人。最後被黑的人數是 3
最大值:1 和 4 自黑,然後剩下的人按照 2,3,7,6,5 的順序行動。這樣一來 3,6,7,8 也
會被黑。於是一共有 6 個人被黑

3.7 資料範圍及約定
• 對於前 30% 的資料,滿足 1 ≤n ≤8
• 對於接下來 30% 的資料,滿足只有前若干個人自黑,後面的第 i 個人只會黑 1… i −1 中
的某一個人
• 對於所有資料,滿足 1 ≤n ≤106

//不會
//太難QAQ

題解
如果 u 要黑 v,我們從 u 到 v 之間連一條邊,很容易發現我們得到了一個基環內向樹森
林。對於每個聯通塊我們單獨考慮。

分 30 ࡽ 3.1
直接爆搜

3.2 接下來 40 分
這一部分的資料,實際上就是隻有單環。單環的節點一定會被黑,因此可以直接從圖中刪
去。於是我們得到了一個森林。
接下來的做法就是從正解的做法中忽略了環的處理。其實很接近正解了

3.3 所有資料
對於每棵環套樹單獨考慮
3.3.1 最大值
1. 如果只有一個單環,那麼顯然被黑
2. 如果只有一個大於 1 的環,那麼可以只剩下一個人
3. 又有環,環上的點又連了一棵樹,那麼我們可以先讓所有度數為 0 的點自黑。可以證明
這樣一來答案不會變差。如果此時只剩下一個環,同情況 1 或者 2。否則答案就是剩下的
點中,入度為 0 的點的個數

3.3.2 最小值
1. 如果只有一個單環,那麼顯然被黑
2. 如果只有一個大於 1 的環,那麼可以隔著黑。
3. 又有環,環上的點又連了一棵樹。我們可以貪心,按照拓撲序依次從底往上黑 (如果可以
黑的話)。如果黑掉了環上某個點,那就可以斷環為鏈了,否則剩下一個環,同情況 1 或
者 2。

時間複雜度和空間複雜度為 O(n)

std

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define drep(i, r, l) for (int i = r; i >= l; i--)
typedef long long ll;
const int N = 1e6 + 8, INF = 1e7;
int n, head[N], tot, L, R, h[N], que[N], cir[N], die[N], f[N], g[N];
int ansmax, ansmin, fa[N], ind[N], cnt, cir_cnt, len, b[N], w[N], q[N], _ind[N];
bool vis[N], inq[N], incir[N], col[N], _vis[N], _die[N];
struct Edge
{
    int next, node;
}e[N << 1];
void add(int x, int y)
{
    e[++tot].next = head[x], head[x] = tot, e[tot].node = y;
    e[++tot].next = head[y], head[y] = tot, e[tot].node = x;
}
int getint()
{
    char c; int num = 0, w = 1;
    for (c = getchar(); !isdigit(c) && c != '-'; c = getchar());
    if (c == '-') c = getchar(), w = -1;
    for (;isdigit(c); c = getchar()) num = num * 10 + c - '0';
    return num * w;
}
void calc_min()
{
    int l = 1, r = R; 
    while (l <= r)
    {
        int u = que[l++]; 
        if (_die[u] || _die[fa[u]]) continue;
        _die[fa[u]] = 1; 
        if (incir[fa[u]]) que[++r] = fa[fa[u]];
    }
    if (r + cir_cnt == cnt) ansmin += (cir_cnt + 1) >> 1;
    rep(i, 1, cnt) ansmin += _die[h[i]];
}
void calc_max()
{
    if (cir_cnt + len == cnt) 
    {
        if (cir_cnt == 1) ansmax += cnt;
        else ansmax += cnt - 1; return;
    }
    int x = 0;
    rep(i, 1, len) 
        if (!incir[fa[que[i]]]) _ind[fa[que[i]]]--;
    rep(i, len + 1, R) if (!_ind[que[i]]) die[que[i]] = -1;
    rep(i, 1, cnt) if (die[h[i]] == -1) x++;
    ansmax += cnt - x;
}
void solve(int rt)
{
    int l = 1, r = 1; h[l] = rt;
    while (l <= r)
    {
        int u = h[l++]; vis[u] = 1;
        for (int i = head[u], v; v = e[i].node, i; i = e[i].next)
            if (!vis[v]) vis[v] = 1, h[++r] = v;
    }
    cnt = r, len = 0;
    rep(i, 1, r) if (!ind[h[i]]) que[++len] = h[i], inq[h[i]] = 1;
    if (!len)
    {
        if (r == 1) ansmax++, ansmin++;
        else ansmax += r - 1, ansmin += (r + 1) / 2;
        return;
    }

    L = 1, R = len;
    while (L <= R)
    {
        int u = que[L++]; 
        ind[fa[u]]--; 
        if (!ind[fa[u]]) que[++R] = fa[u], inq[fa[u]] = 1;
    }
    cir_cnt = 0; 
    rep(i, 1, r) if (!inq[h[i]])
    {
        int tmp = h[i], u = h[i];
        do
        {
            cir[++cir_cnt] = u;
            incir[u] = 1;
            u = fa[u];
        }while (u != tmp);
        break;
    }
    calc_min();
    calc_max();
}
int main()
{
    freopen("shoot.in", "r", stdin);
    freopen("shoot.out", "w", stdout);
    scanf("%d", &n);
    rep(i, 1, n) fa[i] = getint(), ind[fa[i]]++, _ind[fa[i]]++, add(fa[i], i);
    rep(i, 1, n) if (!vis[i]) solve(i);
    printf("%d %d\n", ansmin, ansmax);
    fclose(stdin); fclose(stdout);
    return 0;
}