0929考试
0929考试
T1
容斥一下就好了。
考场上太自信了,以为复杂度能过,就没打容斥,结果下来60pts。(我枯了)
容斥的话直接搜索就可以了,每次找一个数的倍数,两个数的倍数……\(m\)个数的倍数。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
int n, m, ans, res;
int a[21], vis[21], tmp[21];
int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x % y);
}
int lcm(int x) {
long long sum = 1;
for(int i = 1;i <= x; i++) {
sum = sum * tmp[i] / gcd(sum, tmp[i]);
if(sum > n) return 0;
}
return n / sum;
}
void dfs(int now, int to, int last) {
if(now == to) {
res += lcm(now); return ;
}
for(int i = last + 1;i <= m; i++) {
if(!vis[i]) {
vis[i] = 1, tmp[now + 1] = a[i];
dfs(now + 1, to, i);
vis[i] = 0;
}
}
}
int main() {
n = read(); m = read();
for(int i = 1;i <= m; i++) a[i] = read();
for(int i = 1;i <= m; i++) {
res = 0;
dfs(0, i, 0);
if(i & 1) ans += res;
else ans -= res;
}
printf("%d", n - ans);
fclose(stdin); fclose(stdout);
return 0;
}
用二进制加预处理会更快
#include<cstdio>
#include<iostream>
using namespace std;
int n,a[21],m;
int gcd(int a,int b)
{
return a%b?gcd(b,a%b):b;
}
int cnt[1<<21],low[1<<20];
long long g[1<<21];
long long ans;
int lowbit(int i)
{
return i&-i;
}
int main()
{
for(int i=0;i<n;i++) scanf("%d",&a[i]);
for(int i=1;i<1<<n;i++) cnt[i]=cnt[i>>1]+(i&1);
scanf("%d%d",&m,&n);
for(int i=0;i<n;i++) low[1<<i]=i;
g[0]=1;
for(int i=1;i<1<<n;i++) g[i]=g[i-lowbit(i)]/gcd(g[i-lowbit(i)],a[low[lowbit(i)]])*a[low[lowbit(i)]];
for(int i=0;i<1<<n;i++)
{
if(cnt[i]&1) ans-=m/g[i];
else ans+=m/g[i];
}
printf("%d",ans);
fclose(stdin); fclose(stdout);
return 0;
}
T2
考场上错误的暴力拍错误的正解。(我吐了)
真正的正解是二分加单调队列,这我是真没想出来。
首先我们可以知道,对于一段区间,如果它的长度变长,那么它的答案肯定单调不减。
我们每次二分一个第\(k\)大的数,判断是否有大于等于\(k\)的区间的价值比这个大。
我们再找有多少个区间比二分的这个值大的时候可以用单调队列来维护。用两个单调队列,一个维护最大值,一个维护最小值。
具体解释一下代码里那个\(l\)吧:\(l\)表示当前右端点为\(i\)它的合法的区间的个数的下一个。第三个\(while\)那里,如果两个队列不为空,并且最大值减最小值符合条件,这说明有更多的区间符合条件,就令\(l++\),并且将队列里小于\(l\)的弹出。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 4e5 + 5;
int n, cnt, ans, l1, l2, r1, r2;
long long k;
int a[N], q1[N], q2[N];
int judge(int x) {
l1 = l2 = 1; r1 = r2 = 0;
long long res = 0;
for(int i = 1, l = 1;i <= n; i++) {
while(l1 <= r1 && a[q1[r1]] <= a[i]) r1 --;
while(l2 <= r2 && a[q2[r2]] >= a[i]) r2 --;
q1[++r1] = i; q2[++r2] = i;
while(l1 <= r1 && l2 <= r2 && a[q1[l1]] - a[q2[l2]] >= x) {
l ++;
if(q1[l1] < l) l1 ++;
if(q2[l2] < l) l2 ++;
}
res += l - 1;
}
return res >= k;
}
int main() {
n = read(); k = read();
for(int i = 1;i <= n; i++) a[i] = read();
int l = 0, r = 1e9;
while(l < r) {
int mid = (l + r + 1) >> 1;
if(judge(mid)) l = mid;
else r = mid - 1;
}
printf("%d", l);
fclose(stdin); fclose(stdout);
return 0;
}
T3
暴力写挂了。(这也太Cao了)
首先要对每个武器按照攻击力从小到大排序。
\(f[i][j]\)表示在前\(j\)个武器里面选了\(i\)个武器的最大贡献值。先列上转移方程:\(f[i][j] = a[j].y + f[i - 1][j - 1]; \\f[i][j] = max(f[i][j], f[i][j - 1]);\)
为啥要排序?一个大概的解释就是这么做可以算出合法的东西, 如果不排序的话这个位置不好确定啊。假设当前要选第\(i\)个武器,那么可以选这个\(i\)的地方一定是\(j >= i * (r + 1)\),要不前面选出那些攻击力小的就得给它当炮灰了。然后我们枚举第\(i\)个武器选的是\(j\),然后按照上面的那个转移就好了。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 5005, inf = 1e9;
int T, n, r, f[N][N];
struct node { int x, y; } a[N];
int cmp(node a, node b) {
return a.x < b.x;
}
void work() {
sort(a + 1, a + n + 1, cmp);
int m = (n / (r + 1)) + ((n % (r + 1)) > 0), ans = 0;
for(int i = 1;i <= m; i++) {
int t = min(n, i * (r + 1));
for(int j = 0;j < t; j++) f[i][j] = -inf; // 不合法的情况当然要赋上最小值了
for(int j = t;j <= n; j++) f[i][j] = a[j].y + f[i - 1][j - 1];
for(int j = t;j <= n; j++) f[i][j] = max(f[i][j], f[i][j - 1]);
ans = max(ans, f[i][n]);
}
printf("%d\n", ans);
}
int main() {
T = read();
while(T --> 0) {
n = read(); r = read();
for(int i = 1;i <= n; i++) a[i].x = read();
for(int i = 1;i <= n; i++) a[i].y = read();
work();
}
fclose(stdin); fclose(stdout);
return 0;
}
总结
预计得分100 + 60 + 30 = 190, 实际得分60 + 0 + 0 = 60。(mdzz)
暴力打不对????这就很迷。下次千万别犯了。
好好算算时间复杂度,自己搞点极限数据跑一跑。