标签:基于 n+1 top 部分 设计 erase round else memset
题目链接:https://codeforces.com/contest/1303
水题
题意:修一条长度为n的路,要求至少一半是good的。注意在每[1,g]天可以修good的路,然后每[g+1,g+b]天可以修bad的路,依次交替。问按要求修完整条路需要的最短时间。
题解:看了一下数据量可以设计一个很显然的二分算法,二分枚举最短时间x,假如一开始设置了二分的下界是n,那么假如不管good和bad总是可以把路修完的,这时就只需要看这x天里面是不是有至少m=(n+1)/2个good即可。不太清楚O(1)是怎么做的,根本不差这点复杂度,大不了确定一下二分的上下界就可以了。
一个更好的下界,应该是max(n,(m/g-1)(g+b)),更好的上界应该是(n/g+1)(g+b),不过这样也就多缩减了一点点,没啥意义。
题意:构造一个26个字母各出现1次的序列,使得题目给的字符串中,相邻两个字符在序列中也是相邻的。
题解:首先假如有某个字符有多于2种邻居,就直接NO,其次假如成环也是直接NO,否则一定会恰好有两个只有1个邻居的点。相当于就是构造一条欧拉路。简洁的写法是直接用set
char s[205];
set<int> G[128];
bool vis[128];
void dfs(int u, int p) {
if(vis[u] == 1)
return;
putchar(u);
vis[u] = 1;
for(auto &v : G[u]) {
if(v == p)
continue;
dfs(v, u);
}
}
void test_case() {
scanf("%s", s + 1);
int n = strlen(s + 1);
if(n == 1) {
puts("YES");
puts("abcdefghijklmnopqrstuvwxyz");
return;
}
for(int c = 'a'; c <= 'z'; ++c) {
G[c].clear();
vis[c] = 0;
}
for(int i = 2; i <= n; ++i) {
G[s[i]].insert(s[i - 1]);
G[s[i - 1]].insert(s[i]);
}
int cnt1 = 0, head = -1;
for(int c = 'a'; c <= 'z'; ++c) {
if(G[c].size() >= 3) {
puts("NO");
return;
}
if(G[c].size() == 1) {
++cnt1;
head = c;
}
}
if(cnt1 != 2) {
puts("NO");
return;
}
puts("YES");
dfs(head, -1);
for(int c = 'a'; c <= 'z'; ++c)
dfs(c, -1);
putchar('\n');
return;
}
当然比赛时想的就比较蠢了,直接往字母的两边放,这样导致很多条件判断。
char s[200005];
char t[105];
char l[256], r[256];
bool vis[256][256];
void test_case() {
scanf("%s", s + 1);
//puts(s + 1);
int n = strlen(s + 1);
if(n <= 3) {
puts("YES");
if(s[1] == s[3])
n = 2;
if(n == 1) {
puts("xzytabcdefghijklmnopqrsuvw");
return;
}
if(n == 2) {
putchar(s[1]);
putchar(s[2]);
for(int i = 'a'; i <= 'z'; ++i) {
if(i == s[1] || i == s[2])
continue;
putchar(i);
}
putchar('\n');
return;
}
assert(n == 3);
putchar(s[1]);
putchar(s[2]);
putchar(s[3]);
for(int i = 'a'; i <= 'z'; ++i) {
if(i == s[1] || i == s[2] || i == s[3])
continue;
putchar(i);
}
putchar('\n');
return;
}
assert(n > 3);
for(int i = 'a'; i <= 'z'; ++i) {
l[i] = -1;
r[i] = -1;
for(int j = 'a'; j <= 'z'; ++j)
vis[i][j] = 0;
}
for(int i = 2; i <= n; ++i) {
vis[s[i]][s[i - 1]] = 1;
vis[s[i - 1]][s[i]] = 1;
}
int cnt1 = 0, lmost = -1, rmost = -1;
for(int i = 'a'; i <= 'z'; ++i) {
int cnt = 0;
for(int j = 'a'; j <= 'z'; ++j)
cnt += vis[i][j];
if(cnt > 2) {
puts("NO");
return;
}
if(cnt == 1) {
++cnt1;
if(lmost == -1)
lmost = i;
else
rmost = i;
}
}
if(cnt1 != 2) {
puts("NO");
return;
}
for(int i = 2; i <= n; ++i) {
if(l[s[i - 1]] == s[i] || r[s[i - 1]] == s[i])
continue;
if(r[s[i - 1]] == -1 && l[s[i]] == -1) {
r[s[i - 1]] = s[i];
l[s[i]] = s[i - 1];
continue;
}
if(l[s[i - 1]] == -1 && r[s[i]] == -1) {
l[s[i - 1]] = s[i];
r[s[i]] = s[i - 1];
continue;
}
puts("NO");
return;
}
if(r[lmost] == -1)
swap(lmost, rmost);
puts("YES");
memset(t, 0, sizeof(t));
int top = 0;
int cur = lmost;
while(cur != -1) {
t[top++] = cur;
cur = r[cur];
}
printf("%s", t);
for(int i = 'a'; i <= 'z'; ++i) {
int out = 1;
for(int j = 0; j < top; ++j) {
if(t[j] == i) {
out = 0;
break;
}
}
if(out)
putchar(i);
}
putchar('\n');
return;
}
感觉应该会有人挂n=1的case。
题意:有一个容量为x<=1e18的Bag,然后有n个大小为2的非负幂的物品,每次操作可以把一个物品分成等大的两半,求最少的操作把Bag填满。
题解:直接贪心,显然是从x的低位开始构造,先把更低位的物品全部加起来,那么这个范围内的数字都是可以表示,需要的话就直接扣除,毕竟“合并两个等大的物品”是没有花费的。然后假如某位没办法构造的话,就要一直往高位找到第一个有值的,然后一直分两半下来。注意其实这里复杂度应该是一个log的,不过反正两个log也没什么影响。
int cnt[40];
void test_case() {
ll x;
int n;
scanf("%lld%d", &x, &n);
for(int i = 0; i < 32; ++i)
cnt[i] = 0;
ll sum = 0;
for(int i = 1, ai; i <= n; ++i) {
scanf("%d", &ai);
sum += ai;
int k = 0;
while(ai > 1) {
++k;
ai >>= 1;
}
++cnt[k];
}
if(sum < x) {
puts("-1");
return;
}
int ans = 0;
sum = 0;
for(int k = 0; k < 32; ++k) {
sum += 1ll * cnt[k] * (1ll << k);
cnt[k] = 0;
if((x >> k) & 1) {
if(sum >= (1ll << k)) {
sum -= (1ll << k);
continue;
}
while(cnt[k] == 0) {
for(int k1 = k + 1; k1 < 32; ++k1) {
if(cnt[k1]) {
++ans;
--cnt[k1];
cnt[k1 - 1] += 2;
break;
}
}
}
sum += 1ll * cnt[k] * (1ll << k);
cnt[k] = 0;
assert(sum >= (1ll << k));
sum -= (1ll << k);
continue;
}
}
printf("%d\n", ans);
}
注意:
1、左移运算符也可能会导致int溢出,因为x是ll范围的。
2、提交前消除所有的编译器的warning,scanf的lld的warning其实也可以避免,自己写一个返回ll的快读,也不会浪费多少资源。
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(!isdigit(c)) {
if(c == '-')
f = -1;
c = getchar();
}
while(isdigit(c)) {
x = x * 10 + (c - '0');
c = getchar();
}
return x * f;
}
题意:从一个长度为 \(n(1\leq n \leq 400)\) 的字符串 \(s\) 中,进行至多两次操作,问是否能构造出长度为 \(m(1\leq m\leq 400)\) 的字符串 \(t\) 。每次操作,从字符串 \(s\) 中取出一个子序列,接在当前字符串 \(p\) 的后面,初始时, \(p\) 为空串。同一个字符不能被取出2次。
题解:一开始搞了个贪心,结果连第一个样例都过不了,其实看复杂度就知道过不了了。看一下性质(无后效性)其实很容易想到一种 \(dp\) 的算法,但是只想到一个 \(O(n^4)\) 的dp,先枚举字符串 \(t\) 的前半部分和后半部分的分界线,然后用一个 \(dp[i][j][k]\) 表示字符串 \(s\) 的前 \(i\) 个字符是否可以匹配前半的字符串 \(t\) 的前 \(j\) 个字符以及字符串 \(t\) 的后半的前 \(k\) 个字符。这个算法不高效的原因很可能是因为lyd说的“没有把状态压缩到极致”,很明显每个 \(dp\) 只是布尔值的话可以用一些什么bitset优化。别人的正解其实多压缩了一维,也改变了实际上设计的状态,用 \(dp[i][j]\) 表示字符串 \(s\) 的前 \(i\) 个字符匹配字符串 \(t\) 的前半的前 \(j\) 个字符时,最多匹配字符串 \(t\) 的后半的前多少个字符。进行这样的状态压缩,实际上基于一种贪心,因为匹配 \(k\) 个字符可行,那么匹配少于 \(k\) 个字符显然都是可行的。
int n, m;
char s[405], t[805];
int dp[405][805];
bool check(int d) {
for(int i = 0; i <= n; ++i) {
for(int j = 0; j <= m; ++j)
dp[i][j] = -INF;
}
dp[0][0] = 0;
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= d; ++j) {
if(dp[i - 1][j] == m - d)
dp[i][j] = m - d;
else {
if(dp[i - 1][j] >= 0)
dp[i][j] = dp[i - 1][j] + (s[i] == t[d + 1 + dp[i - 1][j]]);
}
}
for(int j = 1; j <= d; ++j) {
if(s[i] == t[j])
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1]);
}
// for(int j = 0; j <= d; ++j)
// printf("dp[%d][%d]=%d\n", i, j, dp[i][j]);
}
// if(dp[n][d] == m - d)
// printf("d=%d\n", d);
return (dp[n][d] == m - d);
}
void test_case() {
scanf("%s%s", s + 1, t + 1);
n = strlen(s + 1), m = strlen(t + 1);
for(int i = 1, j = 1; i <= n; ++i) {
if(s[i] == t[j]) {
++j;
if(j > m) {
puts("YES");
return;
}
}
}
for(int d = 1; d < m; ++d) {
if(check(d)) {
puts("YES");
return;
}
}
puts("NO");
return;
}
收获:
1、dp时不合法状态最好设为正负无穷这种越界值。
2、在进行下标访问的时候要先判断合法性。
3、对于设计出来的状态是布尔值的dp,假如总是有一半是0另一半是1。这种可以用一个整数来压缩。
Educational Codeforces Round 82 (Rated for Div. 2)
标签:基于 n+1 top 部分 设计 erase round else memset
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12302160.html