2016-2017 ACM-ICPC, NEERC, Southern Subregional Contest

2016-2017 ACM-ICPC, NEERC, Southern Subregional Contest
A:
求出数组中的最大值对应的下标 i i i ,当 r [ i ] − R ≤ ∑ j ≠ i r [ j ] − R r[i]-R \leq \sum_{j \neq i} r[j]-R r[i]Rj̸=ir[j]R 时,这个R才有效。然后通过每次取两个最大的出来分别减1就行。对于 R = 0 R=0 R=0 的情况,如果最后只剩1个还需要再减,那么随便选一个另外的再减就行了。

#include <bits/stdc++.h>
using namespace std;
const int N = 107;
int a[N], p[N];
vector<int> ans[N*N];
int n;
struct Node {
    int v, id;
    bool operator < (const Node &rhs) const {
        return v < rhs.v;
    }
};
int main() {
    scanf("%d", &n);
    int mx=-1, r=107, mxid;
    for(int i=0; i<n; ++i) {
        scanf("%d", &a[i]);
        if(a[i]>mx) {
            mx=a[i];
            mxid=i;
        }
        r= min(r, a[i]);
        p[i]=i;
    }
    if(n==2) {
        if(a[0]==a[1]) {
            printf("%d\n", a[0]);
            puts("0");
        } else {
            puts("0");
            printf("%d\n", mx);
            for(int i=0; i<mx; ++i) {
                puts("11");
            }
        }
        return 0;
    }
    for(r; r>0; --r) {
        int sum=0;
        for(int i=0; i<n; ++i) {
            if(mxid!=i) {
                sum+=a[i]-r;
            }
        }
        if(sum>=mx-r) break;
    }
    int sum = 0;
    for(int i=0; i<n; ++i) {
        sum+=a[i]-r;
    }
    int m = 0;
    sort(p, p+n, [](int x, int y) {
            return a[x]>a[y];
        });
    if(sum%2) {
        for(int i=0; i<3; ++i) {
            ans[m].push_back(p[i]);
            a[p[i]] = max(a[p[i]]-1, 0);
        }
        m++;
    }
    priority_queue<Node> q;
    for(int i=0; i<n; ++i) {
        if(a[i]!=r) q.push({a[i], i});
    }
    while(!q.empty()) {
        Node u = q.top(); q.pop();
        --u.v;
        if(q.empty()) {
            ans[m].push_back(u.id);
            ans[m].push_back(u.id==1?2:1);
            m++;
            if(u.v!=r) q.push(u);
        } else {
            Node v = q.top(); q.pop();
            --v.v;
            ans[m].push_back(u.id);
            ans[m].push_back(v.id);
            m++;
            if(v.v!=r) q.push(v);
            if(u.v!=r) q.push(u);
        }
    }
    printf("%d\n", r);
    printf("%d\n", m);
    for(int i=0; i<m; ++i) {
        memset(a, 0, sizeof(a));
        for(int k : ans[i]) a[k]=1;
        for(int j=0; j<n; ++j) printf("%d", a[j]);
        puts("");
    }
}

B:
这题想了一个貌似最优的方法,但是不会证题目中说的最大次数。。。
定义一个mx, mn = split(s) 操作,即将一个集合 s 划分为 mxmn 两个集合。其中 mx 包含可能为最大值的,mn 包含可能为最小值的。
split操作就是两两比对,如果 a[i]>a[j] ,那么将 i 放入 mxj 放入 mn ,反之同理。如果相等,则放入 eq 。之后将 eq 也进行一一比对,直到 eq 为空,那么则划分成功。
首先将初始集合划分为 mnmx 。然后进一步进行划分直到最后 mxmn 只有一个数为止即可。

#include <bits/stdc++.h>
using namespace std;
char s[55];
int query(int x, int y) {
    printf("? %d %d\n", x, y);
    fflush(stdout);
    scanf("%s", s);
    if(s[0]=='>') return 1;
    else if(s[0]=='<') return -1;
    return 0;
}
void split(vector<int>& mn, vector<int>& mx, vector<int> nums) {
    mn.clear();
    mx.clear();
    vector<int> eq;
    if(nums.size()%2) eq.push_back(nums.back());
    for(int i=1; i<nums.size(); i+=2) {
        int r = query(nums[i-1], nums[i]);
        if(r==1) {
            mx.push_back(nums[i-1]);
            mn.push_back(nums[i]);
        } else if(r==-1) {
            mn.push_back(nums[i-1]);
            mx.push_back(nums[i]);
        } else {
            eq.push_back(nums[i]);
        }
    }
//    puts("split to 3 parts");
    while(eq.size()>1) {
        vector<int> neq;
        if(eq.size()%2) neq.push_back(eq.back());
        for(int i=1; i<eq.size(); i+=2) {
            int r = query(eq[i-1], eq[i]);
            if(r==1) {
                mx.push_back(eq[i-1]);
                mn.push_back(eq[i]);
            } else if(r==-1) {
                mn.push_back(eq[i-1]);
                mx.push_back(eq[i]);
            } else {
                neq.push_back(eq[i]);
            }
        }
        eq = neq;
    }
//    puts("split equal");
    if(eq.size()) {
        mx.push_back(eq.back());
        mn.push_back(eq.back());
    }
}
int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n;
        scanf("%d", &n);
        vector<int> mn, mx, eq, tmp;
        for(int i=1; i<=n; ++i) {
            eq.push_back(i);
        }
        split(mn, mx, eq);
        while(mn.size()>1) {
            eq = mn;
            vector<int> tmp;
            split(mn, tmp, eq);
        }
        while(mx.size()>1) {
            eq = mx;
            vector<int> tmp;
            split(tmp, mx, eq);
        }
        printf("! %d %d\n", mn[0], mx[0]);
        fflush(stdout);
    }
}

C:
这题比赛时没看清边数是小于5000的。。。。
看到这个限制条件就比较简单了。对于每个询问bfs,然后问题转化为维护topk个最小值,用优先队列搞一搞就行。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5007;
struct Store {
    int k, p;
    bool operator < (const Store& rhs) const {
        return p<rhs.p;
    }
};
vector<int> adj[N];
vector<int> pts[N];// pts[i][j]表示离i点距离为j的点。
vector<Store> sts[N];
bool vis[N];
int d[N];
int main() {
    int n,m;
    scanf("%d%d", &n, &m);
    for(int i=0; i<m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    int w;
    scanf("%d", &w);
    for(int i=0; i<w; ++i) {
        int c, k, p;
        scanf("%d%d%d", &c, &k, &p);
        sts[c].push_back({k, p});
    }
    int Q;
    scanf("%d", &Q);
    while(Q--) {
        int g,r,a;
        scanf("%d%d%d",&g,&r,&a);
        for(int i=0; i<n; i++) pts[i].clear();
        memset(vis, 0, sizeof(vis));
        vis[g]=1;
        d[g]=0;
        pts[0].push_back(g);
        queue<int> q;
        q.push(g);
        while(!q.empty()) {
            int u = q.front(); q.pop();
            for(int v : adj[u]) {
                if(!vis[v]) {
                    vis[v] = true;
                    d[v]=d[u]+1;
                    q.push(v);
                    pts[d[v]].push_back(v);
//                    printf("deep: %d, pt: %d\n", d[v], v);
                }
            }
        }
        int h;
        int tot = 0;
        ll sum=0;
        priority_queue<Store> pq;
        for(h=0; pts[h].size(); ++h) {
            for(int u : pts[h]) {
                for(auto s : sts[u]) {
                    pq.push(s);
                    tot += s.k;
                    sum += 1LL*s.k*s.p;
                }
            }
            ll res = -1;
            while(tot>=r) {
                auto s = pq.top(); pq.pop();
                if(tot-s.k<r) {
                    res = sum-1LL*(tot-r)*s.p;
                    pq.push(s);
                    break;
                }
                sum -= 1LL*s.k*s.p;
                tot -= s.k;
            }
            if(tot>=r&&res<=a) {
//                cout << tot << " " <<res << endl;
                break;
            }
        }
        if(pts[h].size()) printf("%d\n", h);
        else puts("-1");
    }
}

D:
首先对于每座桥算出 k = 2 l − t k=2l-t k=2lt 为该座桥期望的药效时间。
维护一个之前保留的药效。然后每次吃药都是在快走完的时候吃。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
const int P = 100000;
using ll = long long;
int n;
ll r;
int l[N], t[N], k[N];
vector<ll> p;
int main() {
    scanf("%d%I64d", &n, &r);
    for(int i=0; i<n; ++i) {
        scanf("%d", &l[i]);
    }
    for(int i=0; i<n; ++i) {
        scanf("%d", &t[i]);
        if(t[i]<l[i]) {
            puts("-1");
            return 0;
        }
    }
    for(int i=0; i<n; ++i) k[i]=max(2*l[i]-t[i], 0);
    ll ans = 0;
    ll rem = 0; // 上一阶段剩余的药效
    ll st=0;
    for(int i=0; i<n; ++i) {
        if(rem<k[i]) { //剩余药效不够
            int nd = k[i]-rem; //需要的药效时间
            int c=nd/r; //这一阶段需要吃几粒药
            if(nd%r) {
                c++;
                rem = r-nd%r;
            } else rem = 0;
            ans+=c;
            st += k[i]+(l[i]-k[i])*2;// 更新时间
            if(c&&p.size()<=P) {
                ll b = st-(r-rem);
                for(int i=0; i<c; ++i) {
                    p.push_back(b);
                    b-=r;
                }
            }
        } else { //药效足够维持
            if(rem >= l[i]) { //药效持续全程
                rem -= l[i];
                st += l[i];
            } else {
                st += rem+(l[i]-rem)*2; //更新时间
                rem = 0; //药效失效
            }
 
        }
    }
    printf("%I64d\n", ans);
    if(ans<=P) {
        sort(p.begin(), p.end());
        for(int i=0; i<p.size(); ++i) {
            printf("%I64d%c", p[i], i+1==p.size()?'\n':' ');
        }
 
    }
}

E:
考虑每次揭晓的贡献。对于每一对 ( x , y ) (x, y) (x,y),有先揭晓 x x x ,再揭晓 y y y 或先揭晓 y y y 再揭晓 x x x 两种情况。如果每次揭晓的过程中 x x x y y y 的相对位置发生变化,那么就对答案有贡献 1 1 1

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 107;
int a[N], d[N];
ll rk(int pt, int id) {
    pt += 100;
    ll res = pt*100+(99-id);
//    cout  <<  "res: "<< res <<endl;
    return res;
}
int main() {
    int n;
    scanf("%d", &n);
    int ans = 0;
    for(int i=0; i<n; ++i) scanf("%d%d", &a[i], &d[i]);
    for(int i=0; i<n; ++i) {
        for(int j=i+1; j<n; ++j) {
            int mx = 0;
            int res = 0;
            if((rk(a[i]+d[i], i)-rk(a[j], j))*(rk(a[i], i)-rk(a[j], j))<0) ++res;
            if((rk(a[i]+d[i], i)-rk(a[j]+d[j], j))*(rk(a[i]+d[i], i)-rk(a[j], j))<0) ++res;
            mx = max(mx, res);
//            cout << mx <<endl;
            res = 0;
            if((rk(a[i], i)-rk(a[j]+d[j], j))*(rk(a[i], i)-rk(a[j], j))<0) ++res;
            if((rk(a[i]+d[i], i)-rk(a[j]+d[j], j))*(rk(a[i], i)-rk(a[j]+d[j], j))<0) ++res;
            mx = max(mx, res);
//            printf("i=%d j=%d res=%d\n", i, j, mx);
            ans += mx;
        }
    }
    printf("%d\n", ans);
    return 0;
}

G:
维护已定好的起点和终点,二分找一下就行了。

#include <bits/stdc++.h>
using namespace std;
struct Node {
    int s, t, id;
    bool operator < (const Node& rhs) const {
        return s<rhs.s;
    }
};
const int INF = 2e9;
vector<Node> p;
int main() {
    int n;
    scanf("%d", &n);
    p.push_back({0, 0, -1});
    p.push_back({INF, INF, -1});
    for(int i=0; i<n; ++i) {
        int s, d;
        scanf("%d%d", &s, &d);
        sort(p.begin(), p.end());
        Node kk;
        kk.s=s;
        int id = upper_bound(p.begin(), p.end(), kk)-p.begin();
        int bk = p[id].s; //当前位置后一个的起点
        --id;
        int prev = p[id].t; //当前位置前一个的终点
//        printf("pp %d %d\n", bk, prev);
        if(prev<s&&bk>s+d-1) { //能插入原位置
            p.push_back({s, s+d-1, i});
        } else {
            for(int j=1; j<p.size(); ++j) {
                if(p[j].s-p[j-1].t-1>=d) {
                    p.push_back({p[j-1].t+1, p[j-1].t+d, i});
                    break;
                }
            }
        }
        Node ans = p.back();
        printf("%d %d\n", ans.s, ans.t);
    }
    return 0;
}

H:
先求出答案,再检验就行

#include <bits/stdc++.h>
using namespace std;
string s[107];
string ans;
int a[107];
int main() {
    int n, m;
    cin >> n >> m;
    for(int i=0; i<n; ++i) {
        cin >> s[i];
    }
    int len;
    for(int i=0; i<m; ++i) {
        int t;
        scanf("%d", &t);
        --t;
        a[t]=1;
        if(i==0) len=s[t].length();
        else if(len!=s[t].length()) {
            cout << "No" << endl;
            return 0;
        }
     }
    for(int i=0; i<len; ++i) {
        bool ok = true;
        char ch=0;
        int pp;
        for(int j=0; j<n; ++j) {
            if(!a[j]) continue;
            if(ch==0) ch=s[j][i], pp=j;
            else if(s[j][i]!=ch) {
                ok=false;
                break;
            }
        }
        if(ok) ans.push_back(s[pp][i]);
        else ans.push_back('?');
    }
//    cout << ans << endl;
    for(int i=0; i<n; ++i) {
        if(a[i]) continue;
        if(s[i].length()!=len) continue;
        bool ok=true;
        for(int j=0; j<len; ++j) {
            if(ans[j]==s[i][j]||ans[j]=='?') continue;
            ok = false;
            break;
        }
        if(ok) {
            puts("No");
            return 0;
        }
    }
    puts("Yes");
    cout << ans <<endl;
}

I:
一个人有两种状态,取其中一个就不能取另一个,应该很快想到使用费用流计算。这题是求最大费用最大流,因为用的是SPFA,因此把费用取反即可。
建图的方法很直观:将起点与i连接,容量为1,费用为0 。将i与t1连接,容量为1,费用为a[i]。将i与t2连接,容量为1,费用为b[i]。将t1与t连接,容量为p,费用为0,将t2与t连接,容量为s,费用为0 。
最小费用最大流就是SPFA的复杂度乘上流量,因此最坏情况下每次增广要SPFA一次。假定SPFA的复杂度为 O ( n ) O(n) O(n) ,那么费用流的复杂度是 O ( n f ) O(nf) O(nf)

#include <bits/stdc++.h>
using namespace std;
const int MXN = 3007;
const int M = MXN*10;
struct Edge {
    int to, nxt, cap, flow, cost;
}edge[M];
const int INF = 0x3f3f3f3f;
int head[MXN],tol,pre[MXN],dis[MXN];
bool vis[MXN];
int N;
void init(int n) {
    N=n;
    tol = 0;
    memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int cap, int cost) {
    edge[tol].to = v;
    edge[tol].cap = cap;
    edge[tol].cost = cost;
    edge[tol].flow = 0;
    edge[tol].nxt = head[u];
    head[u] = tol++;
    edge[tol].to = u;
    edge[tol].cap = 0;
    edge[tol].cost = -cost;
    edge[tol].flow = 0;
    edge[tol].nxt = head[v];
    head[v] = tol++;
}
bool spfa(int s, int t) {
    // 沿着最短路增广
    queue<int> q;
    for(int i=0; i<N; ++i) {
        dis[i]=INF;
        vis[i]=false;
        pre[i]=-1; //最短路前驱
    }
    dis[s] = 0;
    vis[s] = true;
    q.push(s);
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i=head[u];i!=-1;i=edge[i].nxt) {
            int v = edge[i].to;
            if(edge[i].cap>edge[i].flow&&dis[v]>dis[u]+edge[i].cost) {
                dis[v] = dis[u]+edge[i].cost;
                pre[v]=i;
                if(!vis[v]) {
                    vis[v] = true;
                    q.push(v);
                }
            }
        }
    }
    if(pre[t]==-1) return false;
    return true;
}
int minCostMaxFlow(int s, int t, int &cost) {
    int flow = 0;
    cost = 0;
    while(spfa(s, t)) {
        int Min = INF;
        for(int i=pre[t]; i!=-1; i=pre[edge[i^1].to]) {
            if(Min>edge[i].cap-edge[i].flow)
                Min = edge[i].cap-edge[i].flow;
        }
        for(int i=pre[t]; i!=-1; i=pre[edge[i^1].to]) {
            edge[i].flow+=Min;
            edge[i^1].flow-=Min;
            cost += edge[i].cost*Min;
        }
        flow += Min;
    }
    return flow;
}
int a[MXN], b[MXN];
int main() {
    int n,p,s;
    scanf("%d%d%d", &n, &p, &s);
    for(int i=0; i<n; ++i) scanf("%d", &a[i]);
    for(int i=0; i<n; ++i) scanf("%d", &b[i]);
    init(n+4);
    int S=n, T=n+1, t1=n+2, t2=n+3;
    for(int i=0; i<n; ++i) {
        addedge(S, i, 1, 0);
        addedge(i, t1, 1, -a[i]);
        addedge(i, t2, 1, -b[i]);
    }
    addedge(t1, T, p, 0);
    addedge(t2, T, s, 0);
    int cost = 0;
    minCostMaxFlow(S, T, cost);
    vector<int> pp, sp;
    for(int i=0; i<n; ++i) {
        for(int j=head[i]; j!=-1; j=edge[j].nxt) {
            int u = edge[j].to;
            if(u==t1&&edge[j].flow) pp.push_back(i+1);
            if(u==t2&&edge[j].flow) sp.push_back(i+1);
        }
    }
    printf("%d\n", -cost);
    for(int i=0; i<pp.size(); ++i) printf("%d%c", pp[i], i+1==pp.size()?'\n':' ');
    for(int i=0; i<sp.size(); ++i) printf("%d%c", sp[i], i+1==sp.size()?'\n':' ');
}

J:
首先很容易求出需要的瓶子数量。
然后用01背包求出拿i个瓶子时,水量为j时最大瓶子容量。
最后扫一遍,只要瓶子容量大于总的水量时就为合法状态。

#include <bits/stdc++.h>
using namespace std;
const int N = 107;
int a[N], b[N], p[N];
int dp[N][N*N];
// dp[i+1][j+b[i]] = dp[i][j]+a[i];
int main() {
    int n;
    scanf("%d", &n);
    int sum = 0;
    for(int i=0; i<n; ++i) {
        scanf("%d", &a[i]);
        sum += a[i];
    }
    for(int i=0; i<n; ++i) {
        scanf("%d", &b[i]);
        p[i]=i;
    }
    sort(p, p+n, [](int x, int y) {
            return b[x]>b[y];
         });
    int tmp=0;
    int t;
    for(int i=0; i<n; ++i) {
        tmp += b[p[i]];
        if(tmp>=sum) {
            t=i+1;
            break;
        }
    }
    memset(dp, -1, sizeof(dp));
    dp[0][0]=0;
    for(int i=0; i<n; ++i) {
        //对于每个瓶子
        for(int j=t; j>=1; --j) {
            // 拿了j个瓶子
            for(int k=sum; k>=a[i]; --k) {
                // 总共拿了水的体积是k,求出对应的最大瓶子体积
                if(dp[j-1][k-a[i]]!=-1)
                    dp[j][k] = max(dp[j][k], dp[j-1][k-a[i]]+b[i]);
//                if(dp[i][k]!=-1) printf("dp[%d][%d]=%d, %d %d\n", i, k, dp[i][k], k-a[j], dp[i-1][k-a[j]]);
            }
        }
    }
    int ans=-1;
    for(int i=sum; i>=0; --i) {
        if(dp[t][i]>=sum) {
//            printf("dp[%d][%d]=%d\n", t, i, dp[t][i]);
            ans = sum-i;
            break;
        }
    }
    printf("%d %d\n", t, ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值