r k 5 rk5 rk5 祭 /dy
T1 sum
给一个正整数 S ≤ 1000 S \leq 1000 S≤1000,要求选出若干个互不相同的正整数,是他们的和不大于 S S S,而且每个数的因数(不包含自身)之和最大。
显然(虽然考试的时候想了很久 qwq)是一个 0 / 1 0/1 0/1 背包。物品就是要选出的正整数,容量是 S S S,然后每个的物品的因数之和。显然因数之和可以暴力 O ( S S ) O(S\sqrt{S}) O(SS) 预处理出来。所以总的复杂度就是 O ( S 2 + S S ) O(S^2 + S\sqrt{S}) O(S2+SS)。
#include<bits/stdc++.h>
using namespace std;
#define MAXN 5050
#define N 1000
int d[MAXN] = { 0 };
int p[MAXN] = { 0 };
int s = 0;
int f[MAXN] = { 0 };
void prework(){
for(int i = 1; i <= N; i++){
memset(d, 0, sizeof(d)); int cnt = 0;
for(int j = 1; j * j <= i; j++)
if(i % j == 0){
d[++cnt] = j;
if(j != i / j) d[++cnt] = i / j;
}
sort(d + 1, d + cnt + 1);
int temp = 0;
for(int j = 1; j <= cnt; j++) temp += d[j];
p[i] = temp - i;
}
}
void dp(){
for(int i = 1; i <= s; i++)
for(int j = s; j >= i; j--)
f[j] = max(f[j], f[j - i] + p[i]);
}
int main(){
scanf("%d", &s);
prework(); dp();
cout << f[s] << '\n';
return 0;
}
T2 sequence
给定一个长度为 n n n 的数组 a a a。给定 L , R L, R L,R,求所有满足长度大于等于 L L L 小于等于 R R R 的 a a a 的子区间的平均值的最大。
显然可以前缀和,然后 n 2 n^2 n2 就能过 60 p t s 60 pts 60pts 然后因为数据太水了,所以又多 A A A 了一个点。就骗了 70 p t s 70 pts 70pts(机房大佬 c m y cmy cmy O ( n 2 ) O(n^2) O(n2) 卡常然后 A A A 掉了 %%%%%)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 100100
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x ;
}
int n = 0;
int l = 0; int r = 0;
int a[MAXN] = { 0 };
int s[MAXN] = { 0 };
signed main(){
n = in; l = in; r = in;
for(int i = 1; i <= n; i++) a[i] = in;
for(int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
double ans = 0;
for(int len = l; len <= r; len++){
for(int R = n; R - len + 1 >= 1; R--){
int L = R - len + 1;
ans = max(ans, double(s[R] - s[L - 1]) / double(R - L + 1));
}
}
printf("%.4lf\n", ans);
return 0;
}
然后是正解。先二分答案,也就是二分平均值,然后每个数减去这个平均值,这个时候所有和大于 0 0 0 的区间的平均值就肯定大于二分的值。
统计减去平均值的后序列的前缀和,问题就变成了前缀和在 [ i − R , i − L ] [i - R, i - L] [i−R,i−L] 区间内是否存在一个数 s j s_j sj 使得 s i > s j s_i > s_j si>sj,然后就是单调队列优化就能做到 O ( n ) O(n) O(n)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define INFI 1e6
#define MAXN 100100
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x ;
}
int n = 0;
int L = 0; int R = 0;
int a[MAXN] = { 0 };
bool check(double avr){
static double s[MAXN]; static double q[MAXN];
static int head; static int tail; static int pos[MAXN];
head = 1; tail = 0;
for(int i = 1; i <= n; i++) s[i] = 1.0 * a[i] - avr + s[i - 1]; // 减后的前缀和
for(int i = L; i <= n; i++){ // 单调队列
while(head <= tail and q[tail] >= s[i - L]) tail--;
while(head <= tail and pos[head] + R < i) head++;
q[++tail] = s[i - L]; pos[tail] = i - L;
if(s[i] >= q[head]) return true;
}
return false;
}
signed main(){
double mid = 0;
n = in; L = in; R = in;
for(int i = 1; i <= n; i++) a[i] = in;
double l = 0; double r = INFI;
for(int t = 1; t <= 100; t++){ // 二分答案 (平均值)
mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.4lf\n", mid);
return 0;
}
T3
就是给定一个无向图,然后让你保证每个边都有且仅有 1 1 1 的入度。也就是转化成最小生成基环树森林。考场上脑抽了,只打了一个最小生成基环树(就是最小生成树然后再加一条没加进去的最小边),然后就有 50 p t s 50 pts 50pts。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 100100
#define MAXM MAXN * 8
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x ;
}
int ans = 0;
int n = 0; int m = 0;
struct Tedge{
int x, y;
int val; bool used;
bool operator < (const Tedge &rhs) const {
return val < rhs.val;
}
}edge[MAXM];
int fa[MAXN] = { 0 };
void init() { for(int i = 1; i <= n; i++) fa[i] = i; }
int find(int x){
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void kruskal(){
for(int i = 1; i <= m; i++){
Tedge e = edge[i];
int fax = find(e.x); int fay = find(e.y);
if(fax == fay) continue;
fa[fax] = fay; ans += e.val; edge[i].used = 1;
}
}
signed main(){
n = in; m = in;
if(m < n) { cout << "No\n"; return 0; }
for(int i = 1; i <= m; i++){
edge[i].x = in, edge[i].y = in, edge[i].val = in;
}
sort(edge + 1, edge + m + 1);
init(); kruskal();
for(int i = 1; i <= n; i++)
if(!edge[i].used) { ans += edge[i].val; break; }
cout << ans << '\n';
return 0;
}
我们考虑怎样加边来维护基环树森林。判断一条边能否加入的时候,如果这两个端点已经连通了,我们就需要知道这个连通块是否有环,如果没有环就连在一起,并标记他现在有环。如果不连通,那么还是判断两个有没有环。如果都有环就不加边,否则就加边并且标记两边是否有环。
就用并查集维护连通性,额外维护联通快中是否有环就可以了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 500500
#define MAXM MAXN * 8
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x ;
}
int n = 0; int m = 0;
int cnt = 0; int ans = 0;
struct Tedge{
int x, y;
int val;
bool operator < (const Tedge &rhs) const {
return val < rhs.val;
}
}edge[MAXM];
int fa[MAXN] = { 0 };
void init() { for(int i = 0; i <= n; i++) fa[i] = i; }
int find(int x){
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
bool isok[MAXN] = { 0 };
void kruskal(){
for(int i = 1; i <= m; i++){
int fax = find(edge[i].x); int fay = find(edge[i].y);
if(fax == fay){
if(!isok[fax]) { isok[fax] = 1; ans += edge[i].val; cnt++; }
} else{
if(isok[fax] and isok[fay]) continue;
fa[fax] = fay; isok[fay] = isok[fay] | isok[fax];
ans += edge[i].val; cnt++;
}
}
}
signed main(){
n = in; m = in;
for(int i = 1; i <= m; i++)
edge[i].x = in, edge[i].y = in, edge[i].val = in;
sort(edge + 1, edge + m + 1);
init(); kruskal();
if(n != cnt) puts("No");
else cout << ans << '\n';
return 0;
}
T4
一棵树,树上的边是一个一个的字符串。然后每次询问有三个值:点 x , y x, y x,y 和字符串 s s s,求 x x x 到 y y y 的路径上有多少个字符串是以 s s s 为前缀的。
看到这个题立马想到 T r i e Trie Trie 树。然后再仔细想一想,要维护一棵可持久化 T r i e Trie Trie,从父节点继承信息。假设询问的串为 s s s,这样可以 O ( ∣ s ∣ ) O(|s|) O(∣s∣) 求一个串是一个点到根节点的路径上的多少串的前缀,记为 a n s ( x ) ans(x) ans(x)。
答案就是 a n s ( x ) + a n s ( y ) − 2 a n s ( l c a ( x , y ) ) ans(x) + ans(y) - 2ans(lca(x, y)) ans(x)+ans(y)−2ans(lca(x,y))。时间复杂度就是 O ( n ∣ s ∣ ) O(n|s|) O(n∣s∣)