南京信息工程大學第十屆程序設計大賽題解

A. 簽到

簡單C語言模擬題。

#include <stdio.h>

double a[5] = {0.01,0.05,0.15,0.3};
int n,p[5];

int main() {
    scanf("%d", &n);
    for(int i=0;i<4;i++) 
        p[i] = 386 * a[i];
    if(n<=p[0]) printf("Winner!");
    else if(n<=p[1]) printf("Au!");
    else if(n<=p[2]) printf("Ag!");
    else if(n<=p[3]) printf("Cu!");
    else printf("Fe...");
    return 0;
}

B. 切蛋糕

由於負鼠自己也要吃蛋糕,所以剛開始要 n+1 。

很容易看出當 n 是2的倍數的情況下,需要切n/2刀,否則至少切 n 刀。

#include <stdio.h>
int T,n;

int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d",&n);
        n++;
        if(n==1) printf("0\n");
        else if(n%2==0) printf("%d\n",n/2);
        else printf("%d\n",n);
    }
    return 0;
}

C. 移動字符串

簡單模擬。

注意,強行模擬 k 次必然超時。可以發現只有當前一個字母全都刪完了後,纔會刪後一個字母,因此,我們可以將k次刪除操作簡化完成。

#include <cstdio>
#include <iostream>

int n, k, ds[30];

using namespace std;

int main() {
    string s;
    cin >> n >> k >> s;
    for (int i = 0; i < s.length(); i++) ds[s[i] - 'a']++;
    for (int i = 0; i < 26; i++)
        if (k > ds[i])
            k -= ds[i];
        else
            ds[i] = k, k = 0;
    for (int i = 0; i < s.length(); i++)
        ds[s[i] - 'a'] ? ds[s[i] - 'a']-- : putchar(s[i]);
    return 0;
}

D. 卡布奇諾

數論+位運算。

每個數乘2或者除2,我們可以通過位運算枚舉出每一個數 aia_i 可以轉變成的數, 以及變成這個數所需要的轉換次數。

用 mp[i] 儲存可以轉變到的數 ii 的次數。

因此最後我們只要找到最大的 ii ,使得 mp[i]nmp[i] \geq n,然後枚舉這個 ii 的2的冪次方倍(必然滿足條件),枚舉得到所要求的最小值。

#include <bits/stdc++.h>
using namespace std;
 
typedef  long long ll;
const int maxn = 100005;
template <class T>
void read(T &x) {
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9') {if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
 
int n,a[maxn],maxi;
int mp[maxn];
 
int cal_one(int a,int x) {
    int res = 0;
    while(x%a) {
        a /= 2;
        res++;
    }
    while(x>a) {
        a *= 2;
        res++;
    }
    return res;
}
 
int calc(int x) {
    int res = 0;
    for(int i=0;i<n;i++) {
        res += cal_one(a[i],x);
    }
    return res;
}
 
int main() {
    read(n);
    for(int i=0;i<n;i++) {
        read(a[i]);
        ll f = a[i];
        while(f) {
            mp[f]++;
            f /= 2;
        }
    }
    int ans = 1e9;
    for(int i=100000;i>=1;i--) {
        if(mp[i]>=n) {
            maxi = i;
            break;
        }
    }
    for(int i=maxi;i<=100000;i*=2) {
        ans = min(ans,calc(i));
    }
    cout << ans << endl;
    return 0;
}

E. 花錢

DFS + 簡單組合數學。

顯然這個圖是一棵樹,可以將圖中任意一個節點作爲樹根建樹。

那麼可以將y作爲樹根,x則是樹y的某一個節點,

設dep[i]爲子樹i的size(包括i本身),節點z爲(y,x)路徑上y的兒子節點

符合條件的對數爲(y,x)路徑兩端的節點數相乘,即dep[x]*(dep[y]-dep[z]),用 n(n1)n * (n-1)減去就可以得出答案。

#include <bits/stdc++.h>
using namespace std;
 
typedef  long long ll;
const int maxn = 300005;
template <class T>
void read(T &x) {
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9') {if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
 
ll n,s,t,cnt1,cnt2,dep[maxn],fa[maxn];
vector<int> e[maxn];
bool vis[maxn];
 
void dfs(int x) {
    vis[x] = 1;
    for(auto i:e[x]) {
        if(!vis[i]) {
            fa[i] = x;
            dfs(i);
            dep[x] += dep[i];
        }
    }
}
 
int main() {
    read(n), read(s), read(t);
    for(int i=0,x,y;i<n-1;i++) {
        read(x), read(y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for(int i=1;i<=n;i++) dep[i]=1;
    dfs(s);
    ll ans = n*(n-1);
    for(int i=t;;i=fa[i]) {
        if(fa[i]==s) {
            cnt1 = dep[s]-dep[i];
            break;
        }
    }
    ans -= cnt1 * dep[t];
    cout << ans << endl;
    return 0;
}

F. 下棋

博弈論。

如果 CY叉JJ 的左上方的話, CY 必勝;

同理,如果Cy叉JJ 的右下方的話, CY 必敗;

然後我們來考慮剩下的情況,由於 叉JJ 可以斜着走,因此,她回到 (0,0)(0,0) 點至少需要移動 max(c,d)max(c,d) 次,而CY則需要移動 a+ba+b 次。如果 a+b>max(c,d)a+b > max(c,d), 那麼叉JJ 必然可以以最短路提前到達終點或堵住 CY 的路, 此時 CY 必敗, 否則 CY 必勝。

#include <bits/stdc++.h>
using namespace std;

int t,a,b,c,d;

int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        if(a<=c&&b<=d) {puts("CY");}
        else if(a>=c && b>=d) {puts("XJJ");}
        else {
            int f = a+b;
            int g = max(c,d);
            if(f<=g) puts("CY");
            else puts("XJJ");
        }
    }
    return 0;
}

G. 學Python

動態規劃問題。

dp[i][j] 表示第i行的縮進有j個Tab時的合法方案數。

  1. 第i行爲’f’,則第i+1行的縮進只能爲j+1。

    dp[i+1][j+1] = dp[i][j]

  2. 第i行爲’s’,則第i+1行的縮進可以爲[0,j]中的任意一種。

    dp[i+1][j] += dp[i][0 to j]

最終結果即爲 i=0ndp[n][i]\sum_{i=0}^{n}dp[n][i]

#include <bits/stdc++.h>
 
using namespace std;
 
const int mo = 1e9 + 7;
 
int dp[5005][5005];
 
int main() {
    int n;
    cin >> n;
    dp[1][0] = 1;
    char s;
    for (int i = 1; i <= n; i++) {
        cin >> s;
        if(s=='f') {
            for(int j=0;j<n;j++)
                dp[i+1][j+1] = dp[i][j];
        } else {
            int sum = 0;
            for(int j=n-1;j>=0;j--) {
                sum = (sum+dp[i][j]) % mo;
                dp[i+1][j] = sum;
            }
        }
    }
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum = (sum + dp[n][i]) % mo;
    }
    cout << sum << endl;
    return 0;
}

H. Sort the String

線段樹 + 計數排序。

由於字母只有26個,所以在給區間[l,r]排序的時候,只需要統計出每個字母的數量,然後再排序。

底下問題就來到了怎麼統計數量以及更新數量。我們可以通過建立26棵線段樹,每棵線段樹維護一個字母在一段區間上的數量。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 5;

int n,q;
char s[maxn];

struct SegmentTree {
    int t[maxn*4], lazy[maxn*4];

    void push_up(int p) {
        t[p] = t[p<<1] + t[p<<1|1];
    }
    
    void push_down(int p,int l,int r) {
        if(lazy[p]!=-1) {
            int mid = (l+r) / 2;
            t[p<<1] = (mid-l+1) * lazy[p];
            t[p<<1|1] = (r-mid) * lazy[p];
            lazy[p<<1] = lazy[p<<1|1] = lazy[p];
            lazy[p] = -1; 
        }
    }

    void build(int p,int l,int r,char c) {
        t[p] = 0, lazy[p] = -1;
        if(l==r) {
            t[p] = (s[l]==c);
            return;
        }
        int mid = (l+r) / 2;
        build(p<<1,l,mid,c);
        build(p<<1|1,mid+1,r,c);
        push_up(p);
    }

    // [L,R] 爲查詢的區間, [l,r] 爲當前區間
    void update(int p,int l,int r,int L,int R,int val) {
        if(L<=l && R>=r) {
            t[p] = (r-l+1) * val;
            lazy[p] =  val;
            return; 
        }
        push_down(p,l,r);
        int mid = (l+r) / 2;
        if(L<=mid) update(p<<1,l,mid,L,R,val);
        if(R>mid) update(p<<1|1,mid+1,r,L,R,val); 
        push_up(p);
    }

    int query(int p,int l,int r,int L,int R) {
        int res = 0;
        if(L<=l && R>=r) return t[p];
        push_down(p,l,r);
        int mid = (l+r) / 2;
        if(L<=mid) res += query(p<<1,l,mid,L,R);
        if(R>mid) res += query(p<<1|1,mid+1,r,L,R);
        push_up(p);
        return res;
    }

    void print(int p,int l,int r,char c) {
        if(l==r) {
            if(t[p]) s[l] = c;
            return;
        }
        push_down(p,l,r);
        int mid = (l+r) / 2;
        if(l<=mid) print(p<<1,l,mid,c);
        if(r>mid) print(p<<1|1,mid+1,r,c);
        push_up(p);
    }
}tr[30];


int cnt[30];

int main() {
    scanf("%d%d%s",&n,&q,s+1);
    for(int i=0;i<26;i++) 
        tr[i].build(1,1,n,i+'a');
    for(int i=0,l,r,op;i<q;i++) {
        scanf("%d%d%d",&l,&r,&op);
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<26;i++) 
            cnt[i] = tr[i].query(1,1,n,l,r);
        for(int i=0;i<26;i++) 
            tr[i].update(1,1,n,l,r,0);

        if(!op) {
            for(int i=25;i>=0;i--) {
                if(!cnt[i]) continue;
                tr[i].update(1,1,n,l,l+cnt[i]-1,1);
                l += cnt[i];
            }
        } else {
            for (int i=0;i<26;i++) {
                if(!cnt[i]) continue;
                tr[i].update(1,1,n,l,l+cnt[i]-1,1);
                l += cnt[i];
            }
        }
    }
    for(int i=0;i<26;i++) 
        tr[i].print(1,1,n,i+'a');
    for(int i=1;i<=n;i++) cout << s[i];
    return 0;
}

I. BJ的貓餅

首先我們注意到,viv_iyiy_i 都是 22 的冪次,因此,(vi)(yi)(∏v_i) * (∏y_i) 等價於 2log2vi+log2yi2^{\sum \log_{2} v_i+\sum \log_{2} y_i},因此,我們僅需要計算 log2vi+log2yi\sum \log_{2} v_i+\sum \log_{2} y_i 的最大值即可

以下部分需要讀者掌握初步的網絡流以及費用流知識,若從未接觸過,請首先出門左轉 Google

好啦相信聰明的讀者已經學會了網絡流和費用流。
本題套用最小費用最大流模板,需要注意的是,本題實際上求的是最大費用最大流,因此給費用加負號再求最小費用即可。求出來的最小費用再加一個負號就是原來的最大價值之和。

我們需要的點有:源點,匯點,超級匯點,nn 個點表示 nn 種普通貓餅,mm 個點表示 mm 種超級貓餅。
從超級源點向每個普通貓餅建邊,容量爲 aiai,費用爲 00。普通貓餅向匯點建邊,容量爲 cici,費用爲 log2(vi)-\log_2(v_i)。再從每種可以製成超級貓餅的普通貓餅,向相對應的超級貓餅建邊,容量爲 INFINF,費用爲 00。這些超級貓餅再向匯點建邊,容量爲 cic'_i,費用爲 log2(yi)-\log_2(y_i)
最後,匯點向超級匯點建邊,容量爲 cc,費用爲 00
在這裏插入圖片描述

#include <bits/stdc++.h>
const int maxn= 2e3+50;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
using namespace std;
struct edge{
    int to, cap, cost, rev;
};
vector<edge>G[maxn];
int dis[maxn];
int pre[maxn], prid[maxn];
int h[maxn];

void init() {
    for (int i = 0; i < maxn; ++i)
        G[i].clear();
    memset(pre, 0, sizeof(pre));
    memset(prid, 0, sizeof(prid));  
    memset(h, 0, sizeof(h));
}
void add_edge(int from, int to, int cap, int cost) {
    G[from].push_back({to, cap, cost, G[to].size()});
    G[to].push_back({from, 0, -cost, G[from].size()-1});
}
void dijkstra(int s) {
    struct point_dis {
        int point;
        int val;
        bool operator < (const point_dis &pd) const {
            return val > pd.val;
        }
    };
    fill(dis, dis+maxn, INF);
    dis[s] = 0;
    priority_queue<point_dis>q;
    q.push({s, dis[s]});
    while (!q.empty()) {
        int now = q.top().point;
        q.pop();
        for (int i = 0; i < G[now].size(); ++i) {
            edge &e = G[now][i];
            if (e.cap > 0 && dis[e.to] > dis[now] + e.cost + h[now] - h[e.to]) {
                dis[e.to] = dis[now] + e.cost + h[now] - h[e.to];
                pre[e.to] = now;
                prid[e.to] = i;
                q.push({e.to, dis[e.to]});
            }
        }
    }
}
pair<int, int> min_cost_max_flow(int s, int t) {
    int flow = 0;
    int cost = 0;
    while (true) {
        dijkstra(s);
        if (dis[t] == INF)
            return {flow, cost};
        for (int i = 0; i < maxn; ++i) {
            h[i] += dis[i];
        }
        int d = INF;
        for (int v = t; v != s; v = pre[v]) {
            d = min(d, G[pre[v]][prid[v]].cap);
        }
        flow += d;
        cost += d*h[t];
        for (int v = t; v != s; v = pre[v]) {
            G[pre[v]][prid[v]].cap -= d;
            G[v][G[pre[v]][prid[v]].rev].cap += d;
        }       
    }
}
long long quick_pow(int t) {
	long long res = 1;
	long long base = 2;
	while (t) {
		if (t & 1)
			res = (res*base) % mod;
		base = (base*base) % mod;
		t >>= 1;
	}
	return res;
}
int main() {
    std::ios::sync_with_stdio(false);
    int n, m, c;
    while (cin >> n >> m >> c) {
        init();
        int start_point = 0, meeting_point = 2048, end_point = 2049;
        for (int i = 1; i <= n; ++i) {
        	int amount;
        	cin >> amount;
        	add_edge(start_point, i, amount, 0);
		}
        for (int i = 1; i <= n; ++i) {
        	int value, volume;
        	cin >> value >> volume;
        	value = log2(value);
        	add_edge(i, meeting_point, volume, -value);
		}		
        for (int i = 1; i <= m; ++i) {
        	int k;
        	cin >> k;
        	while (k--) {
        		int id;
        		cin >> id;
        		add_edge(id, 1e3+i, INF, 0);
			}
        	int value, volume;
        	cin >> value >> volume;
        	value = log2(value);
            add_edge(1e3+i, meeting_point, volume, -value);
        }
        add_edge(meeting_point, end_point, c, 0);
        pair<int, int>ans = min_cost_max_flow(start_point, end_point);
        cout << quick_pow(-ans.second) << endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章