A
如果 问是不是大于
,
回答是,就把
的数覆盖一遍,否则把
的数覆盖一遍,否则把
的覆盖一遍。
如果一个数被覆盖大于一次,就不可能是他
赢当且仅当只有一个数覆盖次数小于等于
发现大于 的可以舍去,有效状态只有一段
,一段
,再一段
.
设 表示依次有
个
,
个
,
个
。决策单调性可以优化到
。
发现答案是 级别,而且关于第三维单调。就可以把第三维和答案交换,优化到
。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 9;
int n, dp[24][N][N];
int main() {
cin >> n;
memset(dp, 0xc0, sizeof(dp));
dp[0][0][0] = 1;
dp[0][0][1] = dp[0][1][0] = 0;
for (int r = 1; r <= 18; ++r) {
for (int i = 0; i <= n; ++i) {
for (int j = 0; i + j <= n; ++j) {
int L = min(i, (1 << (r - 1)) - j);
dp[r][i][j] = max(dp[r][i][j], dp[r - 1][i - L][j]);
dp[r][i][j] = max(dp[r][i][j], (1 << (r - 1)) - j + dp[r - 1][i][j]);
}
}
for (int j = 0; j <= n; ++j) {
int la = j;
for (int i = 0; i + j <= n; ++i) {
bool flag = false;
for (int l = la; l >= 0; --l) {
if (dp[r - 1][i][l] >= j - l) {
flag = true;
dp[r][i][j] = max(dp[r][i][j], dp[r - 1][l][j - l]);
la = l;
break;
}
}
if (!flag)
break;
}
}
}
for (int i = 1; i <= n; ++i) {
int now = 0;
while (dp[now][i - 1][n - i + 1] < 0)
++now;
printf("%d ", now);
}
return 0;
}B
考虑如何刻画涂黑第三个点可以免费第四个点这个条件。
仔细思考一下,发现等价于选择一个点 就表示加入了第
行和第
列。对于任意一个矩形,他所涉及的行
,和列
,只需要其中任意三条边,例如:
,就能表示这四个点。把所有情况枚举出来,发现这四个点联通是充要条件。
那么用点来表示行和列,涂黑一个点 即选择一条边
。需要求如何花费最小的代价使图联通,可知用最小生成树算法。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 9;
vector<int>had[100005];
int fa[N * 2];
int n, m, a, a1, b, c, d, p, s;
int get(int x) {
return x == fa[x] ? x : fa[x] = get(fa[x]);
}
int main() {
scanf("%d%d%d%d%d%d%d", &n, &m, &a, &b, &c, &d, &p);
s = n * m;
a1 = (1ll * a * a * b + 1ll * a * c + d) % p;
for (int i = 0; i < s; i++) {
had[a1].push_back(i);
a1 = (1ll * a1 * a1 * b + 1ll * a1 * c + d) % p;
}
for (int i = 0; i < n + m; i++)
fa[i] = i;
long long ans = 0;
for (int i = 0; i < p; i++) {
for (auto z : had[i]) {
int x = z / m, y = z % m;
if (get(x) != get(n + y)) {
ans += i;
fa[get(n + y)] = get(x);
}
}
}
printf("%lld", ans);
return 0;
}C
从大到小枚举填入数的大小 ,找出
的行和列。
对于这些选出的行列的交点,填一个 可以满足两个条件。对于其他的,填一个
只能满足一次条件。发现这是一个最大匹配的问题,用匈牙利算法即可解决。假设对于数
,有
个满足条件的行列,最大匹配数为
,那么代价就是
。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e3 + 9;
int n, m, k, u, v, b[N], c[N], fa[N], ans = 0;
vector<int> e[N];
bool used[N];
bool dfs(int u) {
for (int i = 0; i < e[u].size(); ++i) {
int v = e[u][i];
if (used[v])
continue;
used[v] = true;
if (!fa[v] || dfs(fa[v])) {
fa[v] = u;
return true;
}
}
return false;
}
signed main() {
scanf("%lld%lld%lld", &n, &m, &k);
for (int i = 1; i <= n; ++i)
scanf("%lld", &b[i]), ans += b[i];
for (int i = 1; i <= n; ++i)
scanf("%lld", &c[i]), ans += c[i];
while (m--) {
scanf("%lld%lld", &u, &v);
if (b[u] == c[v])
e[u].push_back(v);
}
for (int i = 1; i <= n; ++i) {
memset(used, 0, sizeof used);
ans -= dfs(i) * b[i];
}
printf("%lld", ans);
return 0;
}D
因为 ,所以考虑容斥
个格子是否必选。然后看行列对角线是否不能选。然后统计剩余各自的答案。
假设最后留下了 行
列,那么可选的格子就是
。但是对角线的限制比较麻烦。可知如果第
行第
列可以选,但是主对角线不能选,那么可选格子数要
。
同理。可以设三维
背包,每次看是否选择
行,列。然后根据对角线的状态来背包。
时间复杂度
#include <bits/stdc++.h>
using namespace std;
const int N = 35;
const int P = 1e4 + 7;
int C[N * N][N * N];
int n, K, m;
int cnt[1 << 7];
int x[7], y[7];
int vx[N], vy[N];
int f[N][N][N * 2], g[N][N][N * 2];
int solve(int f0, int f1, int ban) {
f[0][0][0] = 1;
int S = 0;
for (int l = 1, r = n; l <= r; l++, r--) {
memset(g, 0, sizeof(g));
for (int i = 0; i <= S; i++)
for (int j = 0; j <= S; j++)
for (int k = 0; k <= 2 * S; k++) {
if (!f[i][j][k])
continue;
if (l != r) {
for (int s1 = vx[l]; s1 <= 1; s1++)
for (int s2 = vy[l]; s2 <= 1; s2++)
for (int s3 = vx[r]; s3 <= 1; s3++)
for (int s4 = vy[r]; s4 <= 1; s4++) {
int ti = i + s1 + s3, tj = j + s2 + s4, tk = k;
if (s1 && s2 && f0)
tk++;
if (s3 && s4 && f0)
tk++;
if (s1 && s4 && f1)
tk++;
if (s2 && s3 && f1)
tk++;
g[ti][tj][tk] = (g[ti][tj][tk] + f[i][j][k]) % P;
}
} else {
for (int s1 = vx[l]; s1 <= 1; s1++)
for (int s2 = vy[l]; s2 <= 1; s2++) {
int ti = i + s1, tj = j + s2, tk = k;
if (s1 && s2 && f0)
tk++;
else if (s1 && s2 && f1)
tk++;
g[ti][tj][tk] = (g[ti][tj][tk] + f[i][j][k]) % P;
}
}
}
if (l != r)
S += 2;
else
S++;
for (int i = 0; i <= S; i++)
for (int j = 0; j <= S; j++)
for (int k = 0; k <= 2 * S; k++)
f[i][j][k] = g[i][j][k];
}
int res = 0;
for (int i = 0; i <= S; i++)
for (int j = 0; j <= S; j++)
for (int k = 0; k <= 2 * S; k++) {
if (!f[i][j][k])
continue;
if ((i + j) % 2 == 0)
res = (res + 1ll * f[i][j][k] * C[i * j - k - ban][m]) % P;
else
res = (res - 1ll * f[i][j][k] * C[i * j - k - ban][m] % P + P) % P;
}
return res;
}
int F(int S) {
memset(vx, 0, sizeof(vx));
memset(vy, 0, sizeof(vy));
int f0 = 0, f1 = 0;
for (int i = 0; i < K; i++)
if (S & (1 << i)) {
vx[x[i]] = 1;
vy[y[i]] = 1;
if (x[i] == y[i])
f0 = 1;
if (x[i] + y[i] == n + 1)
f1 = 1;
}
int res = 0;
res = (res + solve(0, 0, cnt[S])) % P;
if (!f0)
res = (res - solve(1, 0, cnt[S]) + P) % P;
if (!f1)
res = (res - solve(0, 1, cnt[S]) + P) % P;
if (!f0 && !f1)
res = (res + solve(1, 1, cnt[S])) % P;
return res;
}
int main() {
for (int i = 0; i < N * N; i++) {
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; j++)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % P;
}
cin >> n >> K >> m;
for (int i = 0; i < K; i++)
cin >> x[i] >> y[i];
for (int i = 0; i < (1 << K); i++)
cnt[i] = cnt[i >> 1] + (i & 1);
int ans = 0;
for (int i = 0; i < (1 << K); i++) {
m -= cnt[i];
if (cnt[i] & 1)
ans = (ans - F(i) + P) % P;
else
ans = (ans + F(i)) % P;
m += cnt[i];
}
cout << ans << endl;
return 0;
}E
不得不说,对于这种题优先考虑打表是一个不错的选择。打表发现对于任意正整数 都满足。
设 整理得
。枚举
,由韦达定理知
固定 ,计算上面答案对数。
若 满足条件,
也满足条件。因为
顺序不影响,所以
也合法。
发现这很符合打表找到的规律。每次得到 就可以得到
。利用打表得到的特例
递推下去,存在数组里排序二分求解。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int lim = 1e18;
int T, n, a[5000010];
signed main() {
a[n++] = 1;
for (int i = 2; i * i * i <= lim; i++) {
int x = i, y = i * i * i;
a[n++] = y;
while (y <= (lim + x) / i / i) {
x = i * i * y - x;
swap(x, y);
a[n++] = y;
}
}
sort(a, a + n);
scanf("%d", &T);
while (T--) {
int k;
scanf("%lld", &k);
printf("%lld\n", upper_bound(a, a + n, k) - a);
}
}F
直接暴力枚举 即可。但是要保证得到数的方式一定涉及分数,即不整除。
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-5;
int n, m, cnt;
bool ok, no, used[4];
double a[4];
struct node {
int a[4];
} v;
vector<node>A;
void dfs2(int c, bool f) {
if (c == n && fabs(a[0] - m) < eps) {
ok = 1;
if (!f)
no = 1;
return ;
}
for (int i = 0; i < n; i++)
if (fabs(a[i] - floor(a[i])) > eps)
f = 1;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++) {
if (used[i] || used[j])
continue;
double x = a[i], y = a[j];
used[j] = 1;
a[i] = x + y, dfs2(c + 1, f);
a[i] = x - y, dfs2(c + 1, f);
a[i] = y - x, dfs2(c + 1, f);
a[i] = x * y, dfs2(c + 1, f);
if (fabs(y) > eps)
a[i] = x / y, dfs2(c + 1, f);
if (fabs(x) > eps)
a[i] = y / x, dfs2(c + 1, f);
a[i] = x, a[j] = y, used[j] = 0;
}
}
void dfs1(int x, int last) {
if (x == n) {
ok = no = false;
dfs2(1, false);
if (ok && !no) {
v.a[0] = (int)a[0];
v.a[1] = (int)a[1];
v.a[2] = (int)a[2];
v.a[3] = (int)a[3];
A.push_back(v);
}
return ;
}
for (int i = last; i <= 13; i++)
a[x] = i, dfs1(x + 1, i);
}
int main() {
scanf("%d%d", &n, &m);
dfs1(0, 1);
printf("%d\n", cnt = A.size());
for (int i = 0; i < cnt; i++) {
for (int j = 0; j < n; j++)
printf("%d ", A[i].a[j]);
printf("\n");
}
}I
对于操作 ,利用差分可以将区间异或转为单点异或。具体的,作
的差分数组
,有
,则
。 每次区间
异或
改为
单点异或
,最后还原即可。
而操作 乍一看很
,但是按位考虑会发现很有规律。
如看 的第三位。依次是
发现每 位一个循环。那么不妨以
来分类,构造一个表格,第
行第
列表示
。会发现需要异或的是由一个矩阵加上剩下多出来的一个宽为
的矩阵。在二维差分数组上打标记,最后二维前缀和回去。最终
其中
为所有二进制位下,二位前缀和后的异或值。
时间复杂度 ,空间复杂度
,当然可以用 bitset 优化到
的空间。
#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 9;
int n, q;
int a[N], b[N];
int d[N][22];
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
while (q--) {
int op, l, r, x;
scanf("%d%d%d%d", &op, &l, &r, &x);
if (op == 0)
b[l] ^= x, b[r + 1] ^= x;
else {
for (int i = 0; i <= 19; i++)
if ((x & (1 << i)) && (l + (1 << i)) - 1 <= r) {
d[l][i] ^= 1;
b[l] ^= (x >> i) << i;
b[l + (1 << i)] ^= (x >> i) << i;
l += (1 << i);
x += (1 << i);
}
for (int i = 19; i >= 0; i--)
if ((l + (1 << i) - 1) <= r) {
d[l][i] ^= 1;
b[l] ^= (x >> i) << i;
b[l + (1 << i)] ^= (x >> i) << i;
l += (1 << i);
x += (1 << i);
}
}
}
for (int i = 19; i >= 1; i--)
for (int j = 1; j <= n; j++) {
if (!d[j][i])
continue;
d[j][i - 1] ^= 1;
if (j + (1 << i - 1) <= n) {
d[j + (1 << i - 1)][i - 1] ^= 1;
b[j + (1 << i - 1)] ^= (1 << i - 1);
if (j + (1 << i) <= n)
b[j + (1 << i)] ^= (1 << i - 1);
}
}
for (int i = 1; i <= n; i++) {
b[i] ^= b[i - 1];
printf("%d ", a[i]^b[i]);
}
return 0;
}J
如果不存在限制,三角形数目是 ,那考虑容斥用总数减去不合法的个数。
因为两两之间都会有边,那么枚举一个顶点,他的黑边数乘上白边数就是包含他的不合法的三角形的个数。观察一个不合法的三角形,会有一条边和另外两条边颜色不一样。会在这一条边的两个端点都计算一次答案。所以最终计算的不合法的个数要除以 。最后输出
减去不合法的个数
#include <bits/stdc++.h>
using namespace std;
const int N = 8e3 + 9;
namespace GenHelper {
unsigned z1,z2,z3,z4,b,u;
unsigned get() {
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
bool read() {
while (!u) u = get();
bool res = u & 1;
u >>= 1;
return res;
}
void Srand(int x) {
z1=x;
z2=(~x)^0x233333333U;
z3=x^0x1234598766U;
z4=(~x)+51;
u = 0;
}
}
using namespace GenHelper;
bitset<N>e[N];
int main() {
int n, seed ;
long long ans = 0, res = 0;
cin >> n >> seed;
ans = 1ll * n * (n - 1) * (n - 2);
Srand(seed);
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
e[j][i] = e[i][j] = read();
for (int i = 0; i < n; i++) {
int a = e[i].count();
res += 1ll * a * (n - 1 - a);
}
printf("%lld\n", (ans / 6 - (res / 2)));
return 0;
}

全部评论
(0) 回帖