本场链接:Educational Codeforces Round 109 (Rated for Div. 2)
A. Potion-making
可以这么看:因为要让(e+w)最小,又要让(e / (e + w) == k / 100),所以不难想到让(e+w=100,e=k)就是一个可行的方案,可以注意到如果(k,100)之间有公约数就可以约分,那么显然当(k/100)划到不能划的时候分母就取最小值,答案是(100 / gcd(k,100))。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
int gcd(int x,int y)
{
if(y == 0) return x;
return gcd(y,x % y);
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int k;scanf("%d",&k);
printf("%d
",100 / gcd(100,k));
}
return 0;
}
B. Permutation Sort
注意到题目给的反转条件其实无比强力,如果一开始最小值或者最大值已经归位,那么剩下的部分一定能一次完成,如果最大值和最小值刚好位置颠倒,那么需要三次操作:先让一个走到合适的位置再倒两次,其他情况操作两次。
特判:一开始就已经排序了,答案为(0)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
const int N = 55;
int p[N],a[N];
int main()
{
forn(i,1,N - 1) p[i] = i;
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]);
bool ok = 1;
forn(i,1,n) if(a[i] != p[i]) ok = 0;
if(ok) puts("0");
else
{
if(a[1] == 1 || a[n] == n) puts("1");
else if(a[1] == n && a[n] == 1) puts("3");
else puts("2");
}
}
return 0;
}
C. Robot Collisions
注意碰撞的条件是两个机器人走完一秒的时间之后位置相同才会爆炸,手推几组样例之后可以发现能撞上的两个机器人初始位置的奇偶性一定是相同的,正确性:假设两个不同的机器人会相撞,那么某个时间(t)之后两者在同一位置,而奇数和偶数同时加上一个相同的数奇偶性显然不可能相同,所以不会相撞矛盾。
首先将所有机器人按奇偶位置分组,分别处理。注意到:实际上只用管相邻的两个机器人之间撞上的时间,而这样相邻的机器人的对数也就(n-1)个,所以把每一对相邻的两个机器人存入优先队列,每次取出撞上的时间最小的一对机器人赋答案,如果取出的两个有任何一个已经被摧毁了,则跳过本次过程。
但是值得注意的一点是:当两个相邻的机器人撞上摧毁之后,相邻的两个机器人现在会变成相邻的,需要新加入优先队列,所以对每个机器人再维护一个双向链表用于找到相邻机器人的位置信息,支持动态删除。
如此模拟整个过程即可,整体(O(nlogn))。
由于一开始给定的数据不按位置排序,所以还得手动排个序。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
const int N = 3e5+7;
struct Node
{
int x,y,dist;
bool operator<(const Node& r) const
{
return dist > r.dist;
}
};
int pos[N],ans[N],n,m;
char dir[N];
bool st[N];
bool cmp(int x,int y)
{
return pos[x] < pos[y];
}
inline int gdist(int px,int py)
{
int dx = pos[px],dy = pos[py];
if(dir[px] == 'L')
{
if(dir[py] == 'R') return (2 * m - dy + dx) / 2;
else return (dx + dy) / 2;
}
else
{
if(dir[py] == 'R') return (2 * m - dy - dx) / 2;
else return (dy - dx) / 2;
}
}
void solve(vector<int>& a)
{
vector<int> L = vector<int>(n + 7,0),R = vector<int>(n + 7,0);
priority_queue<Node> pq;
forn(i,0,(int)a.size() - 2)
{
pq.push({a[i],a[i + 1],gdist(a[i],a[i + 1])});
if(i != 0) L[a[i]] = a[i - 1];
if(i != n - 1) R[a[i]] = a[i + 1];
}
while(!pq.empty())
{
auto _ = pq.top();pq.pop();
int x = _.x,y = _.y,dist = _.dist;
if(st[x] || st[y]) continue;
ans[x] = ans[y] = dist;
L[R[y]] = L[x];R[L[x]] = R[y];
if(L[x] && R[y]) pq.push({L[x],R[y],gdist(L[x],R[y])});
st[x] = 1;st[y] = 1;
}
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
forn(i,1,n) ans[i] = -1,st[i] = 0;
vector<int> even,odd;
forn(i,1,n) scanf("%d",&pos[i]);getchar();
forn(i,1,n) scanf("%c ",&dir[i]);
forn(i,1,n) if(pos[i] % 2 == 0) even.push_back(i);else odd.push_back(i);
sort(even.begin(),even.end(),cmp);sort(odd.begin(),odd.end(),cmp);
solve(even);solve(odd);
forn(i,1,n) printf("%d ",ans[i]);
puts("");
}
return 0;
}
D. Armchairs
本来的题意不太好描述,把人和凳子换成黑点和白点,问题就是有不超过一半数量的黑点,每个黑点要找一个没用过的白点匹配,并且要支付一个代价,求每个黑点匹配到一个白点的代价总和最小值。
数据范围支持(O(n^2)),使用贪心会喜提WA,考虑dp
:
状态:(f[i][j])表示使用前(i)个白点,已经匹配了前(j)个黑点的代价总和最小值
入口:(f[-][0] = 0)其余(+infin)。
转移:要么不让第(i)个白点与第(j)个黑点匹配,那么(f[i][j] = f[i - 1][j])。否则(f[i][j] = f[i - 1][j - 1] + abs(w[i] - b[j]))。其中(w,b)对应本来的坐标位置。
出口:(f[nw][nb]),其中(nw)是白点的个数,(nb)是黑点的个数。
出口即答案,直接输出即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
const int N = 5005;
int a[N],f[N][N];
int main()
{
int n;scanf("%d",&n);
vector<int> b,w;
forn(i,1,n)
{
scanf("%d",&a[i]);
if(a[i]) b.push_back(i);
else w.push_back(i);
}
memset(f,0x3f,sizeof f);
forn(i,0,n) f[i][0] = 0;
forn(i,1,w.size()) forn(j,1,b.size())
{
f[i][j] = f[i - 1][j];
f[i][j] = min(f[i][j],f[i - 1][j - 1] + abs(w[i - 1] - b[j - 1]));
}
printf("%d
",f[w.size()][b.size()]);
return 0;
}
E. Assimilation IV
由于(n leq 20),考虑状压dp,转移的时候有个非常困难的问题:这道题在计算每个得分被计入的时候是需要排除重复覆盖的情况的,一个得分只会被计入一次,又不可能对(m)进行状压,显然不合理。
考虑直接期望:设指示函数(I(j) = [j-th)得分被得到(])。那么答案(ans = E(sumlimits_{j = 1}^m I(j)))。由于期望的线性性,可以倒换成(ans = sumlimits_{j = 1}^m E(I(J))),特别地,由于指示函数(E(I(J)) = P[I(j) == 1])。所以计算(ans)等价于计算每个得分被得到的概率。
考虑计算每个得分被得到的概率:由于每个得分可能被多个城市覆盖,考虑求每个得分不被得到的概率:枚举每个回合可以选择的城市方案数,第一回合只能用距离是(n+1)的城市,第二回合可以用(n+1)或(n)的城市但是由于此前使用了一个(n+1)的城市,所以相当于是在(cnt[n + 1] + cnt[n] - 1)中取一个元素...以此类推,所以每一回合的方案数可以求出。最后让(n!)求会被得到的方案数,计算结果即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
const int N = 24,M = 5e4+7,MOD = 998244353;
int d[N][M],cnt[N],ans[M];
int qpow(int a,int b,int MOD)
{
int res = 1;
while(b)
{
if(b & 1) res = 1ll * res * a % MOD;
a = 1ll * a * a % MOD;
b >>= 1;
}
return res;
}
int main()
{
init();
int n,m;scanf("%d%d",&n,&m);
forn(i,1,n) forn(j,1,m) scanf("%d",&d[i][j]);
int all = 1;forn(i,1,n) all = 1ll * all * i % MOD;
forn(j,1,m)
{
forn(i,1,n + 1) cnt[i] = 0;
forn(i,1,n) ++cnt[d[i][j]];
int cur = 1;ans[j] = 1;
forr(k,2,n + 1)
{
cur += cnt[k] - 1;
if(cur >= 1) ans[j] = 1ll * ans[j] * cur % MOD;
else
{
ans[j] = 0;
break;
}
}
ans[j] = ((all - ans[j]) % MOD + MOD) % MOD;
}
int res = 0;
forn(j,1,m) res = (1ll * res + ans[j]) % MOD;
res = 1ll * res * qpow(all,MOD - 2,MOD) % MOD;
printf("%d
",res);
return 0;
}