2022“杭电杯”中国大学生算法设计超级联赛(4)题解报告

Problem 1004. Link with Equilateral Triangle

题意:

给定如下图所示的正三角形,左边的边不能填0, 右边的边不能填1, 下边的边不能填2,整个三角新只能填0,1,2,问对于给定边长的三角形是否有合法的填法。

 题解:

对于一个合法的解,应当满足不存在同时包含0,1,2的三角形,下面我们证明这样的三角形一定存在。
左下角必然是1,右下角必然是0,底边不能含有2,则底边上必然有奇数条1-0的边,这些边都属于一个小三角形。考虑其他的0-1边,由于不在两个斜边上,其他的0-1边必然属于两个三角形。因此“每个三角形内0-1边的数量”的和必然为奇数。
但是,假设不存在0-1-2的三角形,则所有三角形都必然包含0条或2条的0-1边,产生了矛盾。
因此一定存在0-1-2的三角形。

代码:

(我都不想粘贴了)

#include<iostream>
using namespace std;

int n;

void solve(){
    cin >> n;
    cout << "No\n";
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1006. BIT Subway

题意:

地铁折扣,官方打折的方案是:对于每个月,如果月初开始之前的购票总和加起来大于等于100,则以后的购票打八折,如果总和大于等于200,则打五折。

而现在有个大聪明认为,他可以将一张票拆成几部分来购买,前一部分凑满折扣,后一部分享受优惠。

现在给定若干张票的票价和购买顺序,要求输出大聪明认为的票价和官方票价。

题解:

模拟或者直接算最终的答案。

代码:

这是直接算答案的,模拟细心点就行了。

#include<iostream>
#include<iomanip>
using namespace std;

const int N = 1e5 + 10;

int n, a[N];

void solve(){
    cin >> n;
    double ans1 = 0, ans2 = 0, sum = 0;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        sum += a[i];
        if(ans2 < 100) ans2 += a[i];
        else if(ans2 >= 100 && ans2 < 200) ans2 += 0.8 * a[i];
        else ans2 += 0.5 * a[i];
    }
    if(sum < 100) ans1 = sum;
    else if(sum >= 100 && sum < 225) ans1 = 100 + (sum - 100) * 0.8;
    else ans1 = 200 + (sum - 225) * 0.5;
    cout << ans1 << ' ' << ans2 << '\n';
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(3);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1011. Link is as bear

题意:

给定一个数列,可以做若干次操作,选择一个l和r,将l到r之间的数都变成这些数的异或和。问将这些数变成同一个数字,这个数字是多大。

题解:

线性基板子直接上

代码:

#include<iostream>
#define int long long
using namespace std;

int n, x, b[100];

void insert(int x){
    for(int i = 62; i >= 0; i --){
        if(x >> i){
            if(b[i]) x ^= b[i];
            else{
                b[i] = x;
                return ;
            }
        }
    }
    return;
}

void solve(){
    cin >> n;
    for(int i = 0; i <= 62; i++){
        b[i] = 0;
    }
    for(int i = 1; i <= n; i ++){
        cin >> x;
        insert(x);
    }
    int ans = 0;
    for(int i = 62; i >= 0; i--){
        ans = max(ans, ans ^ b[i]);
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1007. Climb Stairs

题意:

有若干层塔,塔里有boss,每个boss都有一个血量。对于每一层,可以选择向上揍不超过k层,也可以选择向下一层。能够到达一层的条件是现有的攻击力大于等于boss血量。从0层出发,有一个初始攻击力x,问是否能通关。

题解:

首先,对于在往上k层之内且能往下刷完的,我们一定贪心的选择最低的这样一层,接下来就是查询问题。

我们定义pos为我们当前的位置,now为now一下已将刷完的最高位置,nxt数组为当前往下最多能刷到那里。那我们就从now + 1开始往上扫,能去我们就查询是否可以往下刷完,验证同时更新nxt,这样下次再来到这个点时,就可以直接跳过许多点,跟准确说,每个点就只会被经过两次。

代码:

#include<iostream>
#include<algorithm>
#define int long long
using namespace std;

const int N = 1e5 + 10;

int n, x, k, a[N];
int sum[N], nxt[N];

void solve(){
    cin >> n >> x >> k;
    sum[0] = nxt[0] = 0;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
        nxt[i] = i;
    }
    int pos = 0, now = 0;
    while(now < n){
        bool flag = false;
        for(int i = now + 1; i <= min(pos + k, n); i ++){
            int tmp = i, xx = x;
            while(xx >= a[tmp] && tmp > now){
                xx += sum[tmp] - sum[nxt[tmp - 1]];
                tmp = nxt[tmp - 1];
            }
            nxt[i] = tmp;
            if(tmp <= now){
                flag = true;
                x += sum[i] - sum[now];
                pos = now + 1;
                now = i;
                break;
            }
        }
        if(!flag){
            cout << "NO\n";
            return;
        }
    }
    cout << "YES\n";
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1001. Link with Bracket Sequence II

题意:

给定一个数列,正数表示一种括号的左半部分,负数表示该种括号的有半部分,0表示需要进行填补的。问有多少种不同的合法填补方案。

题解:

区间DP。

定义f(i,j)表示i到j为合法括号序且i和j配对的方案数;g(i,j)为i到j的合法括号序方案数。

那么当i和j位置可以匹配,则更新f(i,j) = k * g(i + 1,j - 1),其中k是i和j相匹配的方案数。

一般的,更新g(i,j)= g(i,k) * f(k + 1,j)

之所以需要f的定义,是因为单纯的区间DP会有重复的情况。

代码:

#include<iostream>
#include<algorithm>
#define int long long
using namespace std;

const int N = 510;
const int mod = 1e9 + 7;

int n, m, a[N];
int f[N][N], g[N][N];

void solve(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j ++){
            f[i][j] = g[i][j] = 0;
        }
    }
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    if(n % 2) {
        cout << 0 << '\n';
        return;
    }
    for(int i = 0; i <= n; i ++){
        g[i + 1][i] = 1;
    }
    for(int len = 2; len <= n; len += 2){
        for(int l = 1; l + len - 1 <= n; l ++){
            int r = l + len - 1, tmp;
            if(a[l] >= 0 && a[r] <= 0){
                if(a[l] == 0 && a[r] == 0) tmp = m;
                else if(a[l] == 0 || a[r] == 0) tmp = 1;
                else if(a[l] + a[r] == 0) tmp = 1;
                else tmp = 0;
                f[l][r] = tmp * g[l + 1][r - 1] % mod;
            }
            for(int k = l; k <= r; k += 2){
                g[l][r] = (g[l][r] + g[l][k - 1] * f[k][r]) % mod;
            }
        }
    }
    cout << g[1][n] << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1003. Magic

题意:

有若干座魔法塔,每一座运作都需要pi能量。塔有一个统一的半径,当对某一座塔加一点能量时,半径内的塔也会加一点能量。还有若干个限制条件,某一段的塔的总能量不能超过一个指定值,问最少需要加多少能量,或输出-1。

题解:

差分约束。

我们令a[i]为前i个魔法塔中加入的魔法原料之和。由于第i个魔法塔的魔法值只与有效半径k内的魔法原料数量有关,则魔法值需求pi可以表示为:

a[min(n, i + k - 1)] - a[max(0, i - k)] ≥ pi;

对于魔法原料添加的限制Lj,Rj,Bj可以表示为:

a[Rj] - a[Lj - 1] ≤ Bj;

对于每个能量塔中原料非负,需要满足:

a[i] = a[i - 1] ≥ 0;

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define INF 0x3f3f3f3f
using namespace std;

const int N = 1e4 + 10;
const int mod = 1e9 + 7;

int n, k, q, top;
int p, l, r, b;
int head[N], dis[N], vis[N], cnt[N];
struct edge{
    int to, cost, next;
}e[N * 3];

void init(){
    top = 0;
    for(int i = 0; i <= n+1; i++){
        memset(vis, 0, sizeof(vis));
        memset(cnt, 0, sizeof(cnt));
        memset(head, -1, sizeof(head));
        memset(dis, -1, sizeof(dis));
    }
}

void add(int u, int v, int w){
    e[top].to = v;
    e[top].cost = w;
    e[top].next = head[u];
    head[u] = top ++;
}

bool spfa(int u){
    queue<int> q;
    q.push(u);
    dis[u] = 0, vis[u] = 1, cnt[u] = 1;
    while(!q.empty()){
        u = q.front(); q.pop();
        vis[u] = 0;
        for(int i = head[u]; i != -1; i = e[i].next){
            int v = e[i].to, w = e[i].cost;
            if(dis[v] < dis[u] + w){
                dis[v] = dis[u] + w;
                cnt[v] = cnt[u] + 1;
                if(cnt[v] > n + 2) return false;
                if(!vis[v]){
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    return true;
}

void solve(){
    init();
    cin >> n >> k;
    for(int i = 1; i <= n; i ++){
        cin >> p;
        add(max(0, i - k), min(n, i + k - 1), p);
        add(i - 1, i, 0);
    }
    cin >> q;
    for(int i = 1; i <= q; i++){
        cin >> l >> r >> b;
        add(r, l - 1, -b);
    }
    for(int i = 0; i <= n; i++) add(n + 1, i, 0);
    if(!spfa(n + 1)) cout << -1 << '\n';
    else cout << dis[n] << '\n';
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int t = 1;
    cin >> t;
    while(t--){
        solve();
    }
}

Problem 1002. Link with Running

题意:

一张图,经过一条边你会消耗一定的体力值和获得一定的健康值。

问在花费最小体力的前提下,获得最高的健康值。

题解:

首先用dijkstra跑出一条最短路,由于图不一定是DAG,因此用tarjan将强联通量缩起来,获得DAG后,跑最长路。

代码:

队友的,太长了不想写了

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<string>
#include<map>
#include<set>
#include<queue>
using namespace std;
#define debug(x) cout<<#x<<": "<<(x)<<endl
#define debug2(x,y) cout<<#x<<":"<<endl;for(int i=1;i<=y;++i)cout<<x[i]<<" ";cout<<endl
#define mem(x,y) memset(x,y,sizeof(x));
#define int long long
#define double long double
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-9;
const double PI=acos(-1.0);
const int maxn=1e6+10;

struct Edge
{
    int to,e,w;
    bool operator<(const Edge &edge)const
    {
        return e<edge.e;
    }
};

int n,m;
vector<Edge> edg[maxn];
vector<Edge> edg2[maxn];
vector<Edge> edg3[maxn];
int dis[maxn];
bool vis[maxn];
int tot=0,dfn[maxn],low[maxn];
int scc[maxn],scctot=0;
int tots=0,s[maxn];
bool ins[maxn];
int ind[maxn],ans[maxn];
int dp[maxn];

void dijkstra()
{
    for(int i=1;i<=n;++i)
    {
        dis[i]=inf;
        vis[i]=0;
    }
    priority_queue<Edge> q;
    dis[1]=0;
    q.push({1,0,0});
    while(!q.empty())
    {
        int fr=q.top().to;
        q.pop();
        if(vis[fr])continue;
        vis[fr]=1;
        for(auto it:edg[fr])
        {
            if(dis[it.to]>dis[fr]+it.e)
            {
                dis[it.to]=dis[fr]+it.e;
                q.push({it.to,-dis[it.to],0});
            }
        }
    }
}

void tarjan(int fr)
{
    dfn[fr]=low[fr]=++tot;
    ins[fr]=1;
    s[++tots]=fr;
    for(auto it:edg2[fr])
    {
        if(dfn[it.to]==0)
        {
            tarjan(it.to);
            low[fr]=min(low[fr],low[it.to]);
        }
        else if(ins[it.to])
        low[fr]=min(low[fr],low[it.to]);
    }
    if(dfn[fr]==low[fr])
    {
        ++scctot;
        while(tots&&s[tots]!=fr)
        {
            scc[s[tots]]=scctot;
            ins[s[tots]]=0;
            --tots;
        }
        scc[s[tots]]=scctot;
        ins[s[tots]]=0;
        --tots;
    }
}

void toposort()
{
    queue<int> q;
    for(int i=1;i<=scctot;++i)
    if(ind[i]==0)q.push(i);
    int tot=0;
    while(!q.empty())
    {
        int fr=q.front();
        q.pop();
        ans[++tot]=fr;
        for(auto it:edg3[fr])
        {
            if(--ind[it.to]==0)q.push(it.to);
        }
    }

    for(int i=1;i<=scctot;++i)
    dp[i]=-inf;
    dp[scc[1]]=0;
    for(int i=1;i<=tot;++i)
    {
        int fr=ans[i];
        for(auto it:edg3[fr])
        {
            dp[it.to]=max(dp[it.to],dp[fr]+it.w);
        }
    }
}

void work()
{
    cin>>n>>m;
    for(int i=1;i<=n;++i)
    {
        edg[i].clear();
        edg2[i].clear();
        edg3[i].clear();
        ind[i]=dfn[i]=low[i]=scc[i]=0;
    }
    int fr,to,e,w;
    for(int i=1;i<=m;++i)
    {
        cin>>fr>>to>>e>>w;
        edg[fr].push_back({to,e,w});
    }
    dijkstra();
    for(int fr=1;fr<=n;++fr)
    {
        // if(dis[fr]==inf)continue;
        for(auto it:edg[fr])
        {
            if(dis[it.to]==dis[fr]+it.e)
            edg2[fr].push_back(it);
        }
    }
    tot=0;tots=0;scctot=0;
    tarjan(1);
    for(int fr=1;fr<=n;++fr)
    {
        for(auto it:edg2[fr])
        {
            if(scc[fr]!=scc[it.to])
            {
                auto t=it;
                t.to=scc[it.to];
                edg3[scc[fr]].push_back(t);
                ++ind[scc[it.to]];
            }
        }
    }
    toposort();
    cout<<dis[n]<<" "<<dp[scc[n]]<<'\n';
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // cout<<fixed<<setprecision(6);
    int _=1;
    cin>>_;
    while(_--)
    work();
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值