南京信息工程大学第十届程序设计大赛题解

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,我们可以通过位运算枚举出每一个数 a i a_i ai 可以转变成的数, 以及变成这个数所需要的转换次数。

用 mp[i] 储存可以转变到的数 i i i 的次数。

因此最后我们只要找到最大的 i i i ,使得 m p [ i ] ≥ n mp[i] \geq n mp[i]n,然后枚举这个 i i i 的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 ∗ ( n − 1 ) n * (n-1) n(n1)减去就可以得出答案。

#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) (0,0) 点至少需要移动 m a x ( c , d ) max(c,d) max(c,d) 次,而CY则需要移动 a + b a+b a+b 次。如果 a + b > m a x ( c , d ) 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 = 0 n d p [ n ] [ i ] \sum_{i=0}^{n}dp[n][i] i=0ndp[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的猫饼

首先我们注意到, v i v_i vi y i y_i yi 都是 2 2 2 的幂次,因此, ( ∏ v i ) ∗ ( ∏ y i ) (∏v_i) * (∏y_i) (vi)(yi) 等价于 2 ∑ log ⁡ 2 v i + ∑ log ⁡ 2 y i 2^{\sum \log_{2} v_i+\sum \log_{2} y_i} 2log2vi+log2yi,因此,我们仅需要计算 ∑ log ⁡ 2 v i + ∑ log ⁡ 2 y i \sum \log_{2} v_i+\sum \log_{2} y_i log2vi+log2yi 的最大值即可

以下部分需要读者掌握初步的网络流以及费用流知识,若从未接触过,请首先出门左转 Google

好啦相信聪明的读者已经学会了网络流和费用流。
本题套用最小费用最大流模板,需要注意的是,本题实际上求的是最大费用最大流,因此给费用加负号再求最小费用即可。求出来的最小费用再加一个负号就是原来的最大价值之和。

我们需要的点有:源点,汇点,超级汇点, n n n 个点表示 n n n 种普通猫饼, m m m 个点表示 m m m 种超级猫饼。
从超级源点向每个普通猫饼建边,容量为 a i ai ai,费用为 0 0 0。普通猫饼向汇点建边,容量为 c i ci ci,费用为 − log ⁡ 2 ( v i ) -\log_2(v_i) log2(vi)。再从每种可以制成超级猫饼的普通猫饼,向相对应的超级猫饼建边,容量为 I N F INF INF,费用为 0 0 0。这些超级猫饼再向汇点建边,容量为 c i ′ c'_i ci,费用为 − log ⁡ 2 ( y i ) -\log_2(y_i) log2(yi)
最后,汇点向超级汇点建边,容量为 c c c,费用为 0 0 0
在这里插入图片描述

#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;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总想玩世不恭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值