A
贪心
有一个1,2,01,2,01,2,0组成的集合,每次选出一个子集,子集和必须被333整除,问最多能选多少个子集?
首先每个000都能单独作为一个合法子集,然后为例让组成的子集尽量多,要选性价比最高的选法,333个111或者333个222,才能组成一个被333整除,但是一个111一个222也行,所以先用一个111加一个222,等1/21/21/2其中某一种用完了,再把剩下的另一种每三个组成一个子集。
作为一个AAA题有点阴了。开始题也读错了,看成能组成多少个333,实际是能拆出多少个子集。。
c
void solve() {
int n;
cin >> n;
vector<int> c(3, 0);
for (int i = 0; i < n; i++) {
int x;
cin >> x;
c[x]++;
}
int ans = c[0];
int mn = min(c[1], c[2]);
ans += mn;
c[1] -= mn;
c[2] -= mn;
if (c[1] > 0) {
ans += c[1] / 3;
}
if (c[2] > 0) {
ans += c[2] / 3;
}
cout << ans << '\n';
}
B
贪心 特判
一个环上,一个人跑,一个人追。每一回合都是跑的人先动,追的人后动。跑的人可以不动,也可以移动一格,最多kkk次,只能移动到相邻点。追的人可以不动,也可以移动一格,不限次数。
跑的人想让被追到的时间最晚,追的人想让追到的时间最早。问追到的时间最早多少?
显然追的人应该从环上较短的一边追,需要的时间就是较短的弧的长度,加上跑的人可以移动的kkk次。
比较阴的是,环长度不超过333的时候要特判,可能有很多移动次数kkk,但是游戏一回合就必定结束。
c
void solve() {
int n, x1, x2, k;
cin >> n >> x1 >> x2 >> k;
if (n <= 3) {
cout << 1 << '\n';
return;
}
int d = abs(x1 - x2);
int ans = min(d, n - d);
cout << ans + k << '\n';
}
C
数位贪心
给一个a<=1e18a<=1e18a<=1e18,给一个数位集合,是0−90-90−9的子集,问只使用集合内的数位,能组成的最接近aaa的数字bbb是多少
如果a,ba,ba,b的数位长度相同,考虑数位dpdpdp的思想,如果a,ba,ba,b再一个前缀上相同,接下来在下一位上,如果ai>bia_i>b_iai>bi,那么后面不论怎么选,都有b<ab\lt ab<a,那么想让bbb尽可能接近aaa,剩下的数位都要选集合内最大的数。
类似地,如果ai<bia_i<b_iai<bi,一定有b>ab\gt ab>a了,那么想让bbb尽可能接近aaa,剩下的数位都要选集合内最小的数。
如果ai=bia_i=b_iai=bi,转化成更小的子问题了,递归看下一位选什么。
按这个思路做一个搜索,就能得到一些候选元素,最接近的bbb就在这些元素里,对这些元素取最优即可。问题在于这样的搜索的复杂度是多少?注意到,如果某一位a,bia_,b_ia,bi不相等了,后面的数位可以全部直接确定,这个分支直接被剪掉了,只有ai=bia_i=b_iai=bi的分支可以一直延申。整个搜索树类似与一个竹竿,一个主干,主干上每个点额外长出两个叶子,整体的点数接近于O(3n)O(3n)O(3n),nnn是数位长度。
如果a,ba,ba,b的数位长度不同,那么可能的解只有两个情况,一是数位长度减一,全部由集合最大数组成,另一个是数位长度加一,全部由集合最小数组成(注意不能是000),把这两个元素也纳入考虑。
c
string s;
vi d;
vector<i128> b;
void dfs(int i, int state, i128 cur) {
if (i == s.size()) {
b.push_back(cur);
return;
}
if (state == 1) {
dfs(i + 1, 1, cur * 10 + d.back());
} else if (state == 2) {
dfs(i + 1, 2, cur * 10 + d.front());
} else {
int y = s[i] - '0';
for (int x : d) {
if (x < y)dfs(i + 1, 1, cur * 10 + x);
else if (x > y)dfs(i + 1, 2, cur * 10 + x);
else dfs(i + 1, 0, cur * 10 + x);
}
}
}
void solve() {
int a, n;
cin >> a >> n;
d.resize(n);
for (int i = 0; i < n; ++i) cin >> d[i];
s = to_string(a);
int L = s.length();
b.clear();
dfs(0, 0, 0);
if (L > 1) {
i128 val = 0;
for (int i = 0; i < L - 1; ++i) val = val * 10 + d.back();
b.push_back(val);
}
int f = -1;
for (int x : d) if (x > 0) {
f = x;
break;
}
if (f != -1) {
i128 val = f;
for (int i = 0; i < L; ++i) val = val * 10 + d.front();
b.push_back(val);
}
i128 A = a;
i128 mn = -1;
for (i128 x : b) {
i128 diff = (A > x) ? (A - x) : (x - A);
if (mn == -1 || diff < mn) {
mn = diff;
}
}
int ans = mn;
cout << ans << '\n';
}
D
扫描线 计数
平面上有一些整点,用两个直线y=k1+0.5,x=k2+0.5y=k_1+0.5,x=k_2+0.5y=k1+0.5,x=k2+0.5划分成四个区域,问有多少种不同的划分方案,满足每个区域至少有一个点。
考虑枚举一条线x=k1+0.5x=k_1+0.5x=k1+0.5,统计另一条线y=k2+0.5y=k_2+0.5y=k2+0.5的方案数。x=k1+0.5x=k_1+0.5x=k1+0.5的有效取值只有O(n)O(n)O(n)个,对所有出现过的xxx排序,两个xxx之间所有取值的答案都是一样的,只用枚举一次。
对于一个k1k_1k1,把点集划分成左右两半,假设是第iii个点,y+k2+0.5y+k_2+0.5y+k2+0.5的取值,需要保证四个区域至少都有一个点,所以为了保证左下,右下都有点,k2k_2k2要不小于左侧的最小yyy何和右侧的最小yyy,也就是k2>=max(premini,sufmini+1)k_2>=\max(premin_i,sufmin_{i+1})k2>=max(premini,sufmini+1),同理,k2k_2k2要小于左侧的最大yyy和右侧的最大yyy,也就是k2<min(premaxi,sufmaxi+1)k_2<\min(premax_i,sufmax_{i+1})k2<min(premaxi,sufmaxi+1)
这就是求在某一个区间内的不同yyy个数,由于点是静态的,把点的yyy拿出来排序,然后二分即可得到个数。
略卡常,除了排序之外都应该是线性的
c
void solve() {
int n;
cin >> n;
vector<pii> p(n + 1);
vi Y;
rep(i, 1, n) {
cin >> p[i].fi >> p[i].se;
Y.push_back(p[i].se);
}
sort(Y.begin(), Y.end());
Y.erase(unique(Y.begin(), Y.end()), Y.end());
vi mn(n + 1, n + 1), mx(n + 1, -1);
rep(i, 1, n) {
int x = p[i].fi, y = p[i].se;
mn[x] = min(mn[x], y);
mx[x] = max(mx[x], y);
}
vi premn, premx, sufmn, sufmx;
rep(i, 1, n) {
int v = mn[i];
if (v != n + 1) {
premn.push_back(v);
sufmn.push_back(v);
}
}
rep(i, 1, n) {
int v = mx[i];
if (v != -1) {
premx.push_back(v);
sufmx.push_back(v);
}
}
int m = premn.size();
if (m < 2) {
cout << 0 << "\n";
return;
}
rep(i, 1, m - 1) {
premn[i] = min(premn[i], premn[i - 1]);
premx[i] = max(premx[i], premx[i - 1]);
}
rep1(i, m - 2, 0) {
sufmn[i] = min(sufmn[i], sufmn[i + 1]);
sufmx[i] = max(sufmx[i], sufmx[i + 1]);
}
int ans = 0;
rep(i, 0, m - 2) {
int l = max(premn[i], sufmn[i + 1]);
int r = min(premx[i], sufmx[i + 1]) - 1;
if (l <= r) {
auto it1 = lower_bound(Y.begin(), Y.end(), l);
auto it2 = upper_bound(Y.begin(), Y.end(), r);
ans += it2 - it1;
}
}
cout << ans << "\n";
}