杭电2021第十次
1003:
签到提,有两种思路可以做这个题
第一个思路是从前往后递推,我们可以去枚举当前有 \(i\) 条线是平行的,然后剩下的 \(n - i\) 条线有不同的情况,但是已经在前面推出来了,可以存在一个容器中,那么交点数的可能就是 \(i * (n - i) + v[n - i][j](1 <= i <= n, 0 <= j <= v[n - i].size()\),复杂度是\(n^4\)的,过不了,可以通过 bitset 优化,相当于用bitset 作为容器,那么就将 一维\(n^2\) 的复杂度用位运算优化了,时间复杂度仍有 \(3e9\) ,可以发现后面都是一段很长的连续,那么对于后面连续的只要全部输出就可以了,那么可以考虑打前面的表观察加上人工二分,在T和wa之间寻找AC,最后可以得到 \(32000\) 是比较优的,所以复杂度 \(\frac {{700}^2 * 32000 + T * 32000} w\),可以过这个题。
第二个思路就是 \(n\) 条线的时候可能的点数就是 \(\frac {n * (n - 1)} 2 - \sum_{x_i} \frac {x_i * (x_i - 1)} 2, \sum_{x_i} = n\),就是枚举所有直线平行的情况,对于某个斜率,有 \(k\) 条直线平行,那么答案就会少 \(k * (k - 1) / 2\),我们可以在递推过程中用一个 \(f_i\) 保存下来得到 \(\sum frac {x_i * (x_i - 1)} 2\) 的最小 \(n\) 值,那么对于大于这个值的同样可以取到这个值,那么求n的答案就是求 \(f_i <= n\) 的情况。复杂度的 \({700}^3 + T * n^2\)
bitset没怎么用过,所以没想到这个,每个数都加上同样的数,刚好契合。
const int maxn = 7e2 + 10;
const int N = 32000;
bitset<N> bt[maxn];
bool vis[maxn * maxn];
void init() {
bt[1][0] = 1;
for(int i = 2; i <= 700; ++ i) {
bt[i][0] = 1;
for(int j = i - 1; j >= 1; -- j) {
bt[i] |= bt[j] << (j * (i - j));
}
//cout << bt[i] << endl;
}
}
void run() {
int n; scanf("%d", &n);
int sz = n * (n - 1) / 2, up = N; printf("0");
for(int i = 1; i < up; ++ i)
if(bt[n][i]) printf(" %d", i);
for(int i = up; i <= sz; ++ i) printf(" %d", i);
puts("");
}
int f[maxn * maxn];
vector<int> qur[701];
vector<int> ans[6];
void init2() {
memset(f, 0x3f, sizeof f); f[0] = 0;
int M = maxn * maxn / 2;
for(int i = 1; i <= 700; ++ i) {
for(int j = 0; j < M; ++ j) {
int k = j + i * (i - 1) / 2;
if(k < M) f[k] = min(f[k], f[j] + i);
}
for(auto it : qur[i]) {
int up = i * (i - 1) / 2;
for(int j = up; j >= 0; -- j) {
if(f[j] <= i) ans[it].push_back(up - j);
}
}
}
for(int i = 0; i < 5; ++ i) if(ans[i].size())
for(int j = 0; j < ans[i].size(); ++ j)
printf("%d%c", ans[i][j], " \n"[j == ans[i].size() - 1]);
}
signed main() {
int t = 1, x;
//init1();
scanf("%d", &t);
for(int i = 0; i < t; ++ i) scanf("%d", &x), qur[x].push_back(i);
init2();
//run();
return 0;
}
1004:一道暴力优化的题目
k个素数直接求容斥的复杂度是枚举子集 \(2^k\),对于 \(T = 1e5\) 不行,想一想就可以想到是不是分块来做,但是当时想不出来怎么搞,因为确实可以对前面的部分素数求 \(lcm\) 来根据 \(lcm\) 分块,但是后面的素数没法处理,每块中这些是不同的。
没想到的就是对于每个整除,可以把除数分为两部分,一部分是前8个,一部分是后8个,那么容斥的过程,我们只要枚举后八个的情况,\(n\) 除掉每个后8个素数的子集后得到的就是只剩下前八个素数的块了,这个可以分块预处理,那么复杂度就变成了 \(T * 2^8\)
也就是对前八个素数预处理,对后8个素数容斥。
const int N = 1 << 8;
const int M = 9699690;
const int FM = 1658880;
int pri[20] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
struct node{
ll a[N], b[N];
int a0, b0;
}a[17];
int f[M + 1];
void init() {
for(int i = 1; i <= 16; ++ i) {
a[i].a[++ a[i].a0] = pri[i];
if(i == 1 || i == 9) continue;
for(int j = 1; j <= a[i - 1].a0; ++ j) {
a[i].a[++ a[i].a0] = a[i - 1].a[j];
a[i].b[++ a[i].b0] = a[i - 1].a[j] * pri[i];
}
for(int j = 1; j <= a[i - 1].b0; ++ j) {
a[i].b[++ a[i].b0] = a[i - 1].b[j];
a[i].a[++ a[i].a0] = a[i - 1].b[j] * pri[i];
}
}
for(int i = 1; i <= 8; ++ i)
for(int j = pri[i]; j <= M; j += pri[i]) f[j] = 1;
for(int i = 1; i <= M; ++ i) f[i] = (!f[i]) + f[i - 1];
//cout << f[M] << endl;
return ;
}
void run() {
ll n, k; scanf("%lld %lld", &n, &k);
ll res = 0;
if(k <= 8) {
res = n;
for(int i = 1; i <= a[k].a0; ++ i) res -= n / a[k].a[i];
for(int i = 1; i <= a[k].b0; ++ i) res += n / a[k].b[i];
printf("%lld\n", res);
}
else {
res += (n / M) * FM + f[n % M];
for(int i = 1; i <= a[k].a0; ++ i) {
ll t = n / a[k].a[i];
res -= (t / M) * FM + f[t % M];
}
for(int i = 1; i <= a[k].b0; ++ i) {
ll t = n / a[k].b[i];
res += (t / M) * FM + f[t % M];
}
printf("%lld\n", res);
}
return ;
}

浙公网安备 33010602011771号