A . Equalize Prices Again
https://codeforces.com/contest/1234/problem/A
相加取平均,向上取整。。
#include
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
int main() {
int t;
cin >> t;
while (t--) {
int n; cin >> n;
int sum = 0;
for (int i = 1; i <= n; i++) {
int inp; scanf("%d", &inp);
sum += inp;
}
double res = (double)sum / n;
cout << (int)ceil(res) << endl;
}
return 0;
}
B. Social Network
https://codeforces.com/contest/1234/problem/B2
离散化,然后手写个队列模拟即可
#include
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
map M;
const int maxn = 2e5 + 7;
int q[maxn], l, r, tot, flag[maxn];
int main() {
int n, k;
cin >> n >> k;
l = 1, r = 0;
for (int i = 1; i <= n; i++) {
int inp;
scanf("%d", &inp);
if (!M.count(inp)) M[inp] = ++tot;
if (flag[M[inp]]) continue;
else {
if (r - l >= k-1) {
flag[M[q[l++]]] = 0;
q[++r] = inp;
flag[M[inp]] = 1;
}
else {
q[++r] = inp;
flag[M[inp]] = 1;
}
}
}
cout << (r - l + 1) << endl;
for (int i = r; i >= l; i--) cout << q[i] << " ";
return 0;
}
C. Pipes
https://codeforces.com/contest/1234/problem/C
注意到 1,2号是一样的,3,4,5,6号是一样的
我就用1号表示1,2号水管, 用2号表示3,4,5,6号水管
可以用一个坐标模拟左上角的水流
注意到假如出现2号,那么它的另外一侧就一定也要是2号
然后写个模拟就可以了
#include
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
map M;
const int maxn = 2e5 + 7;
int num[3][maxn];
int main() {
int t; cin >> t;
string inp1;
while (t--) {
int n; cin >> n;
for (int i = 0; i <= 1; i++) {
cin >> inp1;
for (int j = 1; j <= n; j++) {
num[i][j] = inp1[j - 1] - '0';
if (num[i][j] <= 2) num[i][j] = 1;
else num[i][j] = 2;
}
}
int x = 0, y = 1; //这个表示当前的坐标
int ok = 1;
while (y != n + 1) {
if (num[x][y] == 2 && num[x ^ 1][y] == 2) { //假如都是二号
x ^= 1;
y++;
}
else if (num[x][y] == 1) //假如是一号就前进
y++;
else {
ok = 0;
break;
}
}
if (ok == 0||(y==n+1&&x==0)) {
puts("NO");
}
else {
puts("YES");
}
}
return 0;
}
D. Distinct Characters Queries
https://codeforces.com/contest/1234/problem/D
很明显的线段树,将26个字母用二进制表示,线段树则维护这个二进制
因为只有26个字母,所以线段树的节点合并的时候是常数级别合并
#include
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 7;
int c[maxn<<4];
string inp;
int id = -1;
void build_tree(int l,int r,int ind) {
if (l == r) {
c[ind] = (1 << (inp[++id] - 'a'));
return;
}
int mid = (l + r) >> 1;
build_tree(l, mid, ind << 1);
build_tree(mid + 1, r, ind << 1 | 1);
c[ind] = c[ind << 1] | c[ind << 1 | 1];
}
void update(int l, int r, int aim, int val, int ind) {
if (l == r) {
c[ind] = (1 << val);
return;
}
int mid = (l + r) >> 1;
if (aim <= mid) update(l, mid, aim, val, ind << 1);
else update(mid + 1, r, aim, val, ind << 1 | 1);
c[ind] = c[ind << 1] | c[ind << 1 | 1];
}
int query(int l, int r, int q_l, int q_r, int ind) {
if (q_l <= l && q_r >= r) {
return c[ind];
}
int mid = (l + r) >> 1;
int res1, res2;
res1 = res2 = 0;
if (q_l <= mid) res1 = query(l, mid, q_l, q_r, ind << 1);
if (q_r > mid) res2 = query(mid + 1, r, q_l, q_r, ind << 1 | 1);
return res1 | res2;
}
int main() {
cin >> inp;
int n = inp.size();
build_tree(1, n, 1);
int q; cin >> q;
int opt, in1, in2;
char s[3];
while (q--) {
scanf("%d %d", &opt, &in1);
if (opt == 1) {
scanf("%s", s);
update(1, n, in1, s[0] - 'a', 1);
}
else {
scanf("%d", &in2);
int res, cnt = 0;
res = query(1, n, in1, in2, 1);
for (int i = 0; i < 26; i++)
if ((res >> i) & 1) cnt++;
printf("%d\n", cnt);
}
}
return 0;
}
E. Special Permutations
https://codeforces.com/contest/1234/problem/E
可以看到:
第i个变化为第i+1个的时候,交换的是第1个和第i+1个的坐标
每一次只变化2个数,因此每次只要找这两个数在x中前后的位置的数,作相应的修改就行了
所以用vector数组保存每个q[i]的值对应的所有在q[i]的下标
#include
#define ll long long
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5 + 7;
int q[maxn];
vector v[maxn];
int n, m;
ll res[maxn];
int main() {
cin >> n >> m;
int sto = 0;
for (int i = 1; i <= m; i++) {
scanf("%d", q + i);
v[q[i]].push_back(i); // v存的是q[i]这个值在Q中的下标
}
for (int i = 2; i <= m; i++)
res[1] += 1LL * abs(q[i] - q[i - 1]);
for (int i = 2; i <= n; i++) {
res[i] = res[i - 1];
for (int j = 0; j < v[i - 1].size(); j++) {
for (int k = v[i - 1][j] - 1; k <= v[i - 1][j] + 1; k += 2) {//寻找q[i]前后两个数,并且计算
if (k <= 0 || k > m || q[k] == i || q[k] == i - 1) continue;
res[i] -= (ll)q[k];
if (q[k] > i - 1) res[i] += 1LL;
res[i] += (ll)abs(i - 1 - q[k]);
if (q[k] > i - 1) res[i] -= 1LL;
}
}
for (int j = 0; j < v[i].size(); j++) {
for (int k = v[i][j] - 1; k <= v[i][j] + 1; k += 2) {
if (k <= 0 || k > m || q[k] == i || q[k] == i - 1) continue;
res[i] -= (ll)abs(q[k] - i);
if (q[k] < i) res[i] += 1LL;
res[i] += (ll)q[k];
if (q[k] > i) res[i] -= 1LL;
}
}
}
for (int i = 1; i <= n; i++) cout << res[i] << " ";
return 0;
}
F. Yet Another Substring Reverse
https://codeforces.com/contest/1234/problem/F
这个我竟然没想到状压dp orz。。。
可以选择(或者不选)一段字符串逆转,使得最后那段连续的每个字符都不一样的子串最长
很明显一段字符串可以通过逆转到达原字符串的任意位置,因此题目就变为:
给一段字符串,让你找到里面的两段连续的子字符串,它们的每个字符都不一样,并且加起来的长度最长
题目说字符的数量小于等于20个,2^20大概是(10^6~10^7),据说这里是在疯狂暗示 状压+子集遍历
对于每个连续的子串我都可以其压缩为一个十进制数,那么这里就有一个想法:
用dp[x]表示 被压缩成x的子串的字符个数 ,比如 abc,可以被压缩成 111(二进制),则dp[7]=3
假如我们能够处理出这样的dp数组,就是对原字符串所有的合法子串都用一个十进制数表示(合法子串指:连续+每个字符都不同)
对于某个二进制数:
假如它是合法的,所以具有dp的值,且表示原字符串有一段可以被压缩成这样的子串
寻找它的所有补集的子集:
那么就可以选出答案
遍历完所有状态,再遍历每个状态的补集的子集,就能得到答案。
但是这样是超时的,因为时间只允许我们遍历一次子集,上面的方法是: 遍历所有的子集,对于每一个子集遍历其所有补集的子集,这样就遍历了两次子集
因此我们dp[x]不表示状态为x的时候的字符数,而是表示状态为x的所有子集的字符数的最大值,预处理出这样的dp数组,就可以只遍历一次子集,具体看代码:
#include
using namespace std;
const int maxn = 1 << 20;
int vis[22];
int dp[maxn];
int main() {
string s; cin >> s;
for (int i = 0; i < s.size(); i++) { //这里的dp[x]表示状态为x的字符数
memset(vis, 0, sizeof(vis));
int num = 0, cnt = 0;
for (int j = i; j < s.size(); j++) {
if (vis[s[j] - 'a'])
break;
vis[s[j] - 'a'] = 1;
num |= (1 << (s[j] - 'a'));
cnt++; //cnt:字符数
dp[num] = cnt;
}
}
for (int i = 1; i <= (1 << 20); i++) {//这里的dp[x]表示状态为x以及x的子集的最大字符数
for (int j = 0; j < 20; j++) {
if (i&(1 << j)) {
dp[i] = max(dp[i], dp[i ^ (1 << j)]);
}
else if (i < (1 << j))
break;
}
}
int ans = 0;
for (int i = 1; i < (1 << 20); i++) { //只需要遍历一次子集即可得到答案
ans = max(dp[i] + dp[i ^ ((1 << 20) - 1)], ans);
}
cout << ans << endl;
}