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]−R≤j̸=i∑r[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
划分为 mx
和 mn
两个集合。其中 mx
包含可能为最大值的,mn
包含可能为最小值的。
split操作就是两两比对,如果 a[i]>a[j]
,那么将 i
放入 mx
,j
放入 mn
,反之同理。如果相等,则放入 eq
。之后将 eq
也进行一一比对,直到 eq
为空,那么则划分成功。
首先将初始集合划分为 mn
和 mx
。然后进一步进行划分直到最后 mx
和 mn
只有一个数为止即可。
#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=2l−t 为该座桥期望的药效时间。
维护一个之前保留的药效。然后每次吃药都是在快走完的时候吃。
#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);
}