C: Painting
题目大意
你有数字1~n,对于每个数字i,用且仅用一次刷子将某个区间修改为i。每次刷子的代价为区间长度。总代价最大是多少?
比如刷后的数列是2 1 2 3,那么可以:[1,4]=3,[1,3]=2,[2,2]=1。总共代价为8是最大的。
题解
首先我们可以搞出区间的包含关系,那么对于被一个区间包含的一些区间,如果不和其他子区间邻接,那么就只能修改这个子区间,否则若一些子区间邻接,比如1 1 3 3,我们就可以先改成1 1 1 1再改成1 1 3 3 使得代价最大,显然每次只留一端的一个区间最好,至于选择左端还是右端,dp即可。
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define FOR(i,j,k) for(i=j;i<=k;++i)
const int N = 100005, M = 5005;
vector<int> p[M], sub[M];
int c[N], l[M], r[M], sum[M], dp[M][M];
bool cmp(int a, int b) {
return l[a] < l[b];
}
int work(const vector<int> &lens) {
int i, j, n = lens.size();
FOR(i,1,n) {
dp[i][i] = lens[i - 1];
sum[i] = sum[i - 1] + lens[i - 1];
}
FOR(i,1,n) for (j = i; j; --j)
dp[j][i] = max(dp[j + 1][i], dp[j][i - 1]) + sum[i] - sum[j - 1];
return dp[1][n];
}
int main() {
int n, m, i, j, ans = 0;
scanf("%d%d", &n, &m);
FOR(i,1,n) {
scanf("%d", &c[i]);
p[c[i]].push_back(i);
}
FOR(i,1,m) {
l[i] = p[i].front();
r[i] = p[i].back();
}
FOR(i,1,m) {
int f = 0;
FOR(j,1,m)
if (l[j] < l[i] && r[i] < r[j] && l[j] > l[f])
f = j;
sub[f].push_back(i);
}
FOR(i,0,m) {
sort(sub[i].begin(), sub[i].end(), cmp);
vector<int> lens;
for (j = 0; j < sub[i].size(); ++j) {
if (j != 0 && l[sub[i][j]] != r[sub[i][j - 1]] + 1) {
ans += work(lens);
lens.clear();
}
lens.push_back(r[sub[i][j]] - l[sub[i][j]] + 1);
}
ans += work(lens);
}
printf("%d", ans);
return 0;
}
D: Ones
题目大意
一个算式中,只可以有括号、加法、乘法和1,给定 n(1≤n≤109) n ( 1 ≤ n ≤ 10 9 ) ,构造一个算式使其结果为n,且不使用超过100个1。
题解
注意到如果每次都除2,就是用 (1+1) ( 1 + 1 ) 不断乘起来最后100个1就绰绰有余了。
#include <cstdio>
void work(int x) {
if (x == 1) printf("1");
else if (x == 2) printf("1+1");
else if (x == 3) printf("1+1+1");
else {
if (x & 1) printf("1+");
printf("(1+1)*(");
work(x / 2);
printf(")");
}
}
int main() {
int t, k;
scanf("%d", &t);
while (t--) {
scanf("%d", &k);
work(k);
putchar('\n');
}
return 0;
}
G: Permutation
题目大意
给定一个数列,提取出两个子序列使得一个严格递增一个严格递减,两个子序列覆盖数列所有元素。
题解
结果有3种情况:
/ | \ | \ /
/ | \ | \ /
/ | \ | \ /
\ | / | / \
\ | / | / \
\ | / | / \
如果元素比上升的小,下降的大,那么一定无解。
否则如果比上升的大,就归到上升,比下降的小,就归到下降。
否则就是第二种情况,如果当前元素比下一个元素小,那么一定上升,否则下降。
#include <cstdio>
#include <vector>
using namespace std;
int p[100005];
int main() {
int t, n, i;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
vector<int> R { 0 }, M { n + 1 };
for (i = 0; i < n; ++i)
scanf("%d", &p[i]);
for (i = 0; i < n; ++i) {
if (p[i] < R.back() && p[i] > M.back()) {
puts("NO");
break;
} else if (p[i] < R.back())
M.push_back(p[i]);
else if (p[i] > M.back() || i == n - 1)
R.push_back(p[i]);
else if (p[i] < p[i + 1])
R.push_back(p[i]);
else
M.push_back(p[i]);
}
if (i == n) {
puts("YES");
R.erase(R.begin());
M.erase(M.begin());
printf("%u", R.size());
for (auto &r : R) printf(" %d", r);
printf("\n%u", M.size());
for (auto &m : M) printf(" %d", m);
printf("\n");
}
}
return 0;
}
H: Primes
题目大意
定义
π(x,y)
π
(
x
,
y
)
表示
gcd(x,y)
g
c
d
(
x
,
y
)
的质因子个数,比如
π(30,105)=2,π(8,16)=1,π(2,3)=0
π
(
30
,
105
)
=
2
,
π
(
8
,
16
)
=
1
,
π
(
2
,
3
)
=
0
。定义
在线询问 S(a,b) S ( a , b ) ,询问组数 5⋅104 5 ⋅ 10 4
题解
考虑一个质因子
d
d
对答案的贡献,若d在中的倍数的个数为
x
x
,那么对答案的贡献就是。也就是说,我们枚举
[1,b]
[
1
,
b
]
内的所有质数,求一次即可。
当然不行了质数个数有
8⋅104
8
⋅
10
4
。
对于
x
x
,我们有
为了统一,我们改写:
我们发现,连续的几个质数他们的 x x 可以是一样的,考虑对于每段段相同的质数求一次。
如果 d≥a d ≥ a ,那么 ⌊a−1d⌋=0 ⌊ a − 1 d ⌋ = 0 ,我们只考虑前一项,对于两个素数,如果 ⌊bp1⌋=⌊bp2⌋ ⌊ b p 1 ⌋ = ⌊ b p 2 ⌋ ,那么 ⌊b⌊bp2⌋⌋≤pi≤⌊b⌈bp2⌉⌋ ⌊ b ⌊ b p 2 ⌋ ⌋ ≤ p i ≤ ⌊ b ⌈ b p 2 ⌉ ⌋ 。因此对于这个区间内的所有质数的x都是相等的,我们可以算一整段,每次跳 ⌊b⌊bi⌋⌋ ⌊ b ⌊ b i ⌋ ⌋ 。
如果 d<a d < a ,那么我们每次跳 min{⌊a⌊ai⌋⌋,⌊b⌊bi⌋⌋} min { ⌊ a ⌊ a i ⌋ ⌋ , ⌊ b ⌊ b i ⌋ ⌋ } ,保持 ⌊bp1⌋=⌊bp2⌋and⌊ap1⌋=⌊ap2⌋ ⌊ b p 1 ⌋ = ⌊ b p 2 ⌋ a n d ⌊ a p 1 ⌋ = ⌊ a p 2 ⌋ 就能使整个等式成立。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000005;
bool is_prime[N];
int sum[N];
void sieve() {
memset(is_prime, 1, sizeof is_prime);
for (int i = 2; i < N; ++i) {
if (is_prime[i]) {
for (int j = i + i; j < N; j += i)
is_prime[j] = false;
}
sum[i] = sum[i - 1] + is_prime[i];
}
}
int main() {
int q;
scanf("%d", &q);
sieve();
while (q--) {
int a, b;
long long ans = 0;
scanf("%d%d", &a, &b);
--a;
for (int i = 1, j; i <= b; i = j + 1) {
j = a >= i ? min(a / (a / i), b / (b / i)) : b / (b / i);
long long k = b / i - a / i;
ans += k * (k - 1) / 2 * (sum[j] - sum[i - 1]);
}
printf("%lld\n", ans);
fflush(stdout);
}
return 0;
}
J: Scheduling
题目大意
有 n(1≤n≤100) n ( 1 ≤ n ≤ 100 ) 任务, pi p i 时刻后可以开始执行,必须在 ki k i 时刻前结束,需要 ci c i 的时间执行,执行的时间不必连续。同一时段最多执行 m m 个任务,问是否可行。
题解
由于n很小,我们建立n个节点从原点指向,边权为,对于每个时间点,任务节点连向可行的时间点,容量为1,每个时间点向汇点连容量为m的边跑最大流判断流量是否为 ∑ci ∑ c i 即可。时刻点可以合并。
K
大水题不写了