Codeforces Round #597 (Div. 2)
A - Good ol' Numbers Coloring
题意:有无穷个格子,给定 \(a,b\) ,按以下规则染色: \(0\) 号格子白色;当 \(i\) 为正整数, \(i\) 号格子当 \(i\geq a\) 且 \(i-a\) 是白色格子时涂白色;当 \(i\) 为正整数, \(i\) 号格子当 \(i\geq b\) 且 \(i-b\) 是白色格子时涂白色;仍不是白色的涂黑色,问黑色格子是否有无穷个。
题解:好像做过很多次了,遍历所有的整数的充要条件是这些拿来线性组合的步长的gcd为1。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int T;
scanf("%d", &T);
while(T--) {
int a, b;
scanf("%d%d", &a, &b);
puts(__gcd(a, b) != 1 ? "Infinite" : "Finite");
}
}
B - Restricted RPS
题意:给定 \(a,b,c\) ,必须出 \(a\) 次Rock, \(b\) 次Paper, \(c\) 次Scissors,安排一个合理的顺序使得至少获胜一半的上整。
题解:贪心,直接构造。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[105], t[105];
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int T;
scanf("%d", &T);
while(T--) {
int n, R, P, S;
scanf("%d%d%d%d%s", &n, &R, &P, &S, s + 1);
int cnt = 0;
for(int i = 1; i <= n; ++i) {
t[i] = '.';
if(s[i] == 'R') {
if(P) {
--P;
++cnt;
t[i] = 'P';
}
} else if(s[i] == 'P') {
if(S) {
--S;
++cnt;
t[i] = 'S';
}
} else {
if(R) {
--R;
++cnt;
t[i] = 'R';
}
}
}
if(cnt >= (n + 1) / 2) {
for(int i = 1; i <= n; ++i) {
if(t[i] == '.') {
if(P) {
--P;
t[i] = 'P';
} else if(S) {
--S;
t[i] = 'S';
} else {
--R;
t[i] = 'R';
}
}
}
t[n + 1] = '\0';
puts("YES");
puts(t + 1);
} else
puts("NO");
}
}
C - Constanze's Machine
题意:有一台听写机,会把你说的字母写下来,但是会把"w"写成"uu",并且会把"m"写成"nn",给一个字符串,求它可以由多少种原串经听写机翻译而成。
题解:很显然的一个dp,设 \(dp[i]\) 表示前 \(i\) 位形成的原串的种类数。遇到连续两个特殊字母则会从两个方向转移。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
char s[100005];
int dp[100005];
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
while(~scanf("%s", s + 1)) {
int n = strlen(s + 1);
dp[0] = 1;
for(int i = 1; i <= n; ++i) {
if(s[i] == 'w' || s[i] == 'm')
dp[i] = 0;
else if(i == 1 || s[i] != 'u' && s[i] != 'n')
dp[i] = dp[i - 1];
else if(s[i] == s[i - 1])
dp[i] = (dp[i - 1] + dp[i - 2]) % MOD;
else
dp[i] = dp[i - 1];
}
printf("%d\n", dp[n]);
}
}
注:这样写看起来好像会有bug,貌似 \(i-2\) 有可能会跳过那个0进行转移,但事实上不可能,因为必须要两个全等的"u"或者"n"才能跨步跳转,比如"mnn"这个串,并不会影响结果。
D - Shichikuji and Power Grid
题意:有 \(n\) 个城市,要给他们供电,直接给城市供电修建发电厂消耗 \(c_i\) 元,连接 \(i\) 城市和 \(j\) 城市的线路是曼哈顿距离,价格是每单位 \(k_i+k_j\) ,求最小价格使得所有城市都有电。
题解:好像和之前校内组队做的一个毁灭世界的很像,直接用最小生成树搞。方法是把 \(0\) 号节点和每个节点连一条权为 \(c_i\) 的边,然后“在这个城市修建发电厂”就等价于“从0号超级免费发电厂修价格为 \(c_i\) 的输电电路给这个城市供电”,转化成简单的最小生成树问题。
注:添加了0号节点之后,一开始是有 \(n+1\) 个连通块。记得要给多出来的边预留位置,应该是2000个吧,但是没关系直接开5e6就可以了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int x[2005], y[2005];
int c[2005], k[2005];
struct Edge {
int u, v;
ll w;
Edge() {}
Edge(int uu, int vv, ll ww) {
u = uu, v = vv, w = ww;
}
bool operator<(const Edge &e)const {
return w < e.w;
}
} e[5000005];
int etop;
int fa[2005], cnt;
void init(int n) {
for(int i = 0; i <= n; ++i)
fa[i] = i;
cnt = n + 1;
}
int find(int x) {
int r = fa[x], t;
while(fa[r] != r)
r = fa[r];
while(fa[x] != r) {
t = fa[x];
fa[x] = r;
x = t;
}
return r;
}
bool merge(int x, int y) {
int fx = find(x), fy = find(y);
if(fx == fy)
return false;
fa[fx] = fy;
return true;
}
vector<int> ans1;
vector<pair<int, int> > ans2;
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d%d", &x[i], &y[i]);
for(int i = 1; i <= n; ++i)
scanf("%d", &c[i]);
for(int i = 1; i <= n; ++i)
scanf("%d", &k[i]);
for(int i = 1; i <= n; ++i) {
e[++etop] = Edge(0, i, c[i]);
for(int j = i + 1; j <= n; ++j)
e[++etop] = Edge(i, j, (1ll * abs(x[i] - x[j]) + abs(y[i] - y[j])) * (1ll * k[i] + k[j]));
}
sort(e + 1, e + 1 + etop);
init(n);
ll sumw = 0;
for(int i = 1; i <= etop && cnt > 1; ++i) {
int u = e[i].u, v = e[i].v;
ll w = e[i].w;
if(merge(u, v)) {
sumw += w;
if(u == 0)
ans1.push_back(v);
else
ans2.push_back({u, v});
}
}
printf("%lld\n", sumw);
// sort(ans1.begin(), ans1.end());
printf("%d\n", (int)ans1.size());
for(int i = 0, siz = ans1.size(); i < siz; ++i)
printf("%d%c", ans1[i], " \n"[i == siz - 1]);
// sort(ans2.begin(), ans2.end());
printf("%d\n", (int)ans2.size());
for(int i = 0, siz = ans2.size(); i < siz; ++i)
printf("%d %d\n", ans2[i].first, ans2[i].second);
}
标签:最小生成树,Kruskal算法
E - Hyakugoku and Ladders
题意:给一个10*10的方格图,从左下角走到右上角,路径是S型,图中有一些垂直的梯子,可以选择爬梯子或者不爬梯子,不能从梯子中间下来也不能换乘其他梯子(每一步最多只能走一个梯子),求最小期望。
细节:投出的骰子点数越界时,强制留在原地,否则强制前进骰子的点数。投点并尝试移动后在新位置假如是梯子底部则可以选择是否传送到该梯子的梯子顶部(每一步最多只能走一个梯子)。
题解:摆明了就是一个期望dp。麻烦的地方在于要给格子编号,然后用一个数组简单记录梯子传送去哪里,最后几步有可能会停留在原点造成循环转移。事实上这道题比F简单。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int id[15][15], cg[15][15], g[105];
double dp[105];
bool vis[105];
double calc_dp(int p) {
if(vis[p])
return dp[p];
dp[p] = 0.0;
vis[p] = 1;
if(p >= 95) {
if(p == 100)
return dp[p];
double sum = 6.0;
for(int i = 1; i <= 100 - p; ++i)
sum += calc_dp(p + i);
sum /= 1.0 * (100 - p);
dp[p] = sum;
return dp[p];
}
double sum = 6.0;
for(int i = 1; i <= 6; ++i) {
if(g[p + i] == 0)
sum += calc_dp(p + i);
else
sum += min(calc_dp(p + i), calc_dp(g[p + i]));
}
sum /= 6.0;
dp[p] = sum;
return dp[p];
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
memset(vis, 0, sizeof(vis));
for(int i = 10; i >= 1; --i) {
for(int j = 1; j <= 10; ++j)
scanf("%d", &cg[i][j]);
}
int top = 0;
for(int i = 1; i <= 10; ++i) {
if(i & 1)
for(int j = 1; j <= 10; ++j)
id[i][j] = ++top;
else
for(int j = 10; j >= 1; --j)
id[i][j] = ++top;
}
for(int i = 1; i <= 10; ++i) {
for(int j = 1; j <= 10; ++j) {
if(cg[i][j])
g[id[i][j]] = id[i + cg[i][j]][j];
}
}
printf("%.10f\n", calc_dp(1));
}
标签:模拟,期望dp
F - Daniel and Spring Cleaning
题意:询问 \([l,r]\) 区间内有多少个数对 \((a,b)\) 满足 \(a+b=a \oplus b\) 。
思路:看起来就不能进位,这个不用多说,按直觉来看就是当 \(a,b\) 的二进制位1的位置不重复的时候才成立。这个形式就非常数位dp,再来一点场外因素肯定自己的猜测。但是至于怎么设状态还是不知道。
参考别人的题解:求区间中有多少个二元组满足条件就用二维容斥,具体为:设 \(f(a,b)\) 为满足 \(x+y=x \oplus y\) 且 \(0\leq x\leq a\) 且 \(0\leq y\leq b\) 的 \((x,y)\) 的数量,则答案为 \(f(l,r)=f(r,r)-2f(l-1,r)+f(l-1,l-1)\) 。剩下的直接抄模板。问题是什么才是二维数位dp的好模板呢?只能去偷别人的了。
这个数位dp就是直接连数位相关的的状态都没有的,因为每一位都是独立的,dfs下去的时候一直都保存合法的状态(i1&&j1直接被continue了),根据这么多年数位dp的经验,t肯定是表示这一位取值是否受限,而q是表示这一位是否可以取0作为开头(去掉也没关系,实测),而因为每次的限制位不一样所以对t值不为0的dp是不能重复使用的,不过为了方便全部设置成-1也可以(画蛇添足不记忆t状态就会TLE)。now==-1的时候返回的是越界之后(全部确定之后)还合法的数字的数量,在这里是恒为合法,直接返回1。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[35][2][2][2][2], d1[35], d2[35];
inline ll dfs(ll now, int t1, int t2, int q1, int q2) {
if(now == -1)
return 1;
ll &res = dp[now][t1][t2][q1][q2];
if(res != -1)
return res;
res = 0;
int u1 = t1 ? d1[now] : 1, u2 = t2 ? d2[now] : 1;
for(int i = 0; i <= u1; i++)
for(int j = 0; j <= u2; j++) {
if(i == 1 && j == 1)
continue;
res += dfs(now - 1, t1 && (i == u1), t2 && (j == u2), q1 || (i != 0), q2 || (j != 0));
}
return res;
}
inline ll calc(ll x, ll y) {
if(x < 0 || y < 0)
return 0;
memset(dp, -1, sizeof(dp));
for(ll i = 0; i <= 30; i++)
d1[i] = (x >> i) & 1;
for(ll i = 0; i <= 30; i++)
d2[i] = (y >> i) & 1;
return dfs(30, 1, 1, 0, 0);
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t;
scanf("%d", &t);
while(t--) {
ll l, r;
scanf("%lld%lld", &l, &r);
printf("%lld\n", calc(r, r) - 2ll * calc(l - 1, r) + calc(l - 1, l - 1));
}
}
标签:容斥原理,数位dp