Hello, 2020!
codechef - SADPAIRS:求点双,建出圆方树,然后对于 G 相同的点集建虚树 + 差分一下即可求出答案。唯一需要注意的可能是图不一定连通,可以建虚点解决。
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 400000;
const int MAXG = 1000000;
struct Graph{
struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt;
Graph() {ecnt = edges;}
void addedge(int u, int v) {
// printf("! %d %d\n", u, v);
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
}G1, G2, G3;
#define rep(G, x) for(Graph::edge *p=G.adj[x];p;p=p->nxt)
typedef long long ll;
int dfn[MAXN + 5], low[MAXN + 5], stk[MAXN + 5], tp, dcnt, cnt;
void dfs1(int x, int f) {
dfn[x] = low[x] = (++dcnt);
rep(G1, x) {
if( p->to == f ) continue;
if( !dfn[p->to] ) {
stk[++tp] = p->to;
dfs1(p->to, x), low[x] = min(low[x], low[p->to]);
if( low[p->to] >= dfn[x] ) {
G2.addedge(++cnt, x);
do {
G2.addedge(cnt, stk[tp]);
}while( stk[tp--] != p->to );
}
}
else low[x] = min(low[x], dfn[p->to]);
}
}
int fa[20][MAXN + 5], dep[MAXN + 5];
void dfs2(int x, int f) {
fa[0][x] = f;
for(int i=1;i<20;i++)
fa[i][x] = fa[i-1][fa[i-1][x]];
dfn[x] = (++dcnt), dep[x] = dep[f] + 1;
rep(G2, x) {
if( p->to == f ) continue;
dfs2(p->to, x);
}
}
int lca(int x, int y) {
if( dep[x] < dep[y] ) swap(x, y);
for(int i=19;i>=0;i--)
if( dep[fa[i][x]] >= dep[y] )
x = fa[i][x];
if( x == y ) return x;
for(int i=19;i>=0;i--)
if( fa[i][x] != fa[i][y] )
x = fa[i][x], y = fa[i][y];
return fa[0][x];
}
int N, E;
ll ans[MAXN + 5], res[MAXN + 5];
vector<int>v[MAXG + 5];
bool cmp(int x, int y) {
return dfn[x] < dfn[y];
}
int a[MAXN + 5], tot;
void insert(int x) {
if( !tp ) stk[++tp] = x;
else {
int l = lca(x, stk[tp]);
while( tp && dfn[l] < dfn[stk[tp]] ) {
int y = stk[tp--]; a[++tot] = y;
if( !tp || dfn[l] > dfn[stk[tp]] )
stk[++tp] = l, G3.addedge(l, y);
else G3.addedge(stk[tp], y);
}
stk[++tp] = x;
}
}
bool tag[MAXN + 5];
int build(int i) {
tot = 0;
for(int j=0;j<v[i].size();j++)
insert(v[i][j]), tag[v[i][j]] = true;
while( tp ) {
int y = stk[tp--]; a[++tot] = y;
if( tp ) G3.addedge(stk[tp], y);
}
return a[tot];
}
int siz[MAXN + 5];
void dfs3(int x, int f) {
siz[x] = tag[x];
rep(G3, x) {
if( p->to == f ) continue;
dfs3(p->to, x), siz[x] += siz[p->to];
}
}
void dfs4(int x, int f, int tot) {
ll del = 0, tmp = tag[x];
rep(G3, x) {
if( p->to == f ) continue;
if( x == N + 1 )
dfs4(p->to, x, siz[p->to]);
else {
dfs4(p->to, x, tot);
ll k = 1LL*siz[p->to]*(tot - siz[p->to]);
res[fa[0][p->to]] += k, res[x] -= k;
}
del += tmp*siz[p->to], tmp += siz[p->to];
}
ans[x] += del + 1LL*tmp*(tot - siz[x]);
}
void clear() {
G3.ecnt = G3.edges;
for(int i=1;i<=tot;i++)
G3.adj[a[i]] = NULL, tag[a[i]] = false;
}
void dfs5(int x, int f) {
rep(G2, x) {
if( p->to == f ) continue;
dfs5(p->to, x), res[x] += res[p->to];
}
ans[x] += res[x];
}
int main() {
scanf("%d%d", &N, &E), cnt = N + 1;
for(int i=1;i<=N;i++) {
int G; scanf("%d", &G);
v[G].push_back(i);
}
for(int i=1;i<=E;i++) {
int a, b; scanf("%d%d", &a, &b);
G1.addedge(a, b);
}
for(int i=1;i<=N;i++)
if( !dfn[i] ) dfs1(i, 0), G2.addedge(N + 1, i);
dcnt = 0, dfs2(N + 1, 0);
for(int i=1;i<=MAXG;i++)
if( v[i].size() ) {
sort(v[i].begin(), v[i].end(), cmp);
int rt = build(i); dfs3(rt, 0), dfs4(rt, 0, siz[rt]), clear();
}
dfs5(N + 1, 0);
for(int i=1;i<=N;i++)
printf("%lld\n", ans[N + 1] + ans[i]);
}
atcoder - AGC030D:考虑每一对数的贡献。从后往前作 dp,定义 f[0/1][i][j][k] 表示经过最后 i 次操作,有多少种可能 j 在 k 前面/后面。i 一维可以滚动数组。用一些 trick 就可以得到 O(n^2) 的算法。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 3000;
const int MOD = int(1E9) + 7;
inline int add(int x, int y) {return (x + y >= MOD ? x + y - MOD : x + y);}
inline int sub(int x, int y) {return (x - y < 0 ? x - y + MOD : x - y);}
inline int mul(int x, int y) {return 1LL*x*y%MOD;}
int pw2[MAXN + 5];
void init() {
pw2[0] = 1;
for(int i=1;i<=MAXN;i++)
pw2[i] = mul(2, pw2[i-1]);
}
int f[2][MAXN + 5][MAXN + 5], lst[MAXN + 5][MAXN + 5];
void update(int l, int r, int k) {
f[0][l][r] = mul(f[0][l][r], pw2[lst[l][r] - k - 1]);
f[1][l][r] = mul(f[1][l][r], pw2[lst[l][r] - k - 1]);
lst[l][r] = k;
}
void transform(int &x, int &y) {
int t = (x + y) % MOD;
x = y = t;
}
int A[MAXN + 5], X[MAXN + 5], Y[MAXN + 5];
int main() {
init();
int N, Q; scanf("%d%d", &N, &Q);
for(int i=1;i<=N;i++) scanf("%d", &A[i]);
for(int i=1;i<=Q;i++) scanf("%d%d", &X[i], &Y[i]);
for(int i=1;i<=N;i++)
for(int j=i+1;j<=N;j++)
f[0][i][j] = 1, lst[i][j] = Q + 1;
for(int i=Q;i>=1;i--) {
if( X[i] > Y[i] ) swap(X[i], Y[i]);
for(int j=1;j<=X[i]-1;j++) {
update(j, X[i], i), update(j, Y[i], i);
transform(f[0][j][X[i]], f[0][j][Y[i]]);
transform(f[1][j][X[i]], f[1][j][Y[i]]);
}
for(int j=X[i]+1;j<=Y[i]-1;j++) {
update(X[i], j, i), update(j, Y[i], i);
transform(f[0][X[i]][j], f[1][j][Y[i]]);
transform(f[1][X[i]][j], f[0][j][Y[i]]);
}
for(int j=Y[i]+1;j<=N;j++) {
update(X[i], j, i), update(Y[i], j, i);
transform(f[0][X[i]][j], f[0][Y[i]][j]);
transform(f[1][X[i]][j], f[1][Y[i]][j]);
}
update(X[i], Y[i], i);
transform(f[0][X[i]][Y[i]], f[1][X[i]][Y[i]]);
}
int ans = 0;
for(int i=1;i<=N;i++)
for(int j=i+1;j<=N;j++) {
update(i, j, 0);
if( A[i] < A[j] ) ans = add(ans, f[1][i][j]);
if( A[i] > A[j] ) ans = add(ans, f[0][i][j]);
}
printf("%d\n", ans);
}
codechef - CSUBSQ:先容斥转成 |max - min| < W。固定选 i 为 max,则需要询问 [i-W+1, i-1] 求个背包的结果。分治,每次处理中点 mid 到两边的背包,利用这个信息处理跨越中点询问区间。最后只有两个背包 + 一个物件,直接 O(K) 枚举。时间复杂度 O(NKlogN)。加了取模优化是真的快。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXK = 50;
const int MAXN = 100000;
const int MOD = int(1E9) + 7;
int add(int x, int y) {return (x + y >= MOD ? x + y - MOD : x + y);}
int f[MAXK + 5][MAXN + 5], g[MAXK + 5][MAXN + 5], ans;
int l[MAXN + 5], r[MAXN + 5], A[MAXN + 5];
int N, K, W;
int get() {
for(int i=0;i<K;i++)
for(int j=0;j<=N;j++)
f[i][j] = 0;
f[0][0] = 1;
for(int j=1;j<=N;j++)
for(int i=0;i<K;i++)
f[i][j] = add(f[i][j-1], f[i<A[j]?i-A[j]+K:i-A[j]][j-1]);
return f[0][N];
}
void update(int k) {
ans = (ans >= k ? ans - k : ans + MOD - k);
}
void divide_conquer(int L, int R) {
if( L == R ) {
if( l[L] == r[L] )
update(A[L] == 0), update(A[L] + A[L-1] == 0 || A[L] + A[L-1] == K);
else if( l[L] > r[L] )
update(A[L] == 0);
return ;
}
int M = (L + R) >> 1;
for(int i=0;i<K;i++) f[i][M + 1] = 0;
f[0][M + 1] = 1;
for(int j=M;j>=L;j--)
for(int i=0;i<K;i++)
f[i][j] = add(f[i][j+1], f[i<A[j]?i-A[j]+K:i-A[j]][j+1]);
for(int i=0;i<K;i++) g[i][M] = 0;
g[0][M] = 1;
for(int j=M+1;j<=R;j++)
for(int i=0;i<K;i++)
g[i][j] = add(g[i][j-1], g[i<A[j]?i-A[j]+K:i-A[j]][j-1]);
for(int i=M+2;i<=R+1&&i<=N;i++)
if( L <= l[i] && l[i] <= M ) {
for(int j=0;j<K;j++)
update(1LL*f[j][l[i]]*g[(K-j+K-A[i])%K][r[i]]%MOD);
}
divide_conquer(L, M), divide_conquer(M + 1, R);
}
void solve() {
scanf("%d%d%d", &N, &K, &W);
for(int i=1;i<=N;i++)
scanf("%d", &A[i]), l[i] = max(1, i - W + 1), r[i] = i - 1;
ans = get(); update(1);
if( W ) divide_conquer(1, N);
printf("%d\n", ans);
}
//i - W < j
int main() {
int T; scanf("%d", &T);
while( T-- ) solve();
}
atcoder - AGC035C:N = 2^M 无解。N = 2^M - 1 时,取 P = 2^(M-1),连 (i+P)+N -> i -> P -> (i+P) -> i+N (1 <= i < P) 即可,P+N 随便连向一个 i+N。若 N = 2^M - 1 + R,前面的 2^M - 1 一样构造,连 P -> 2^M,剩下的 k 只需要找对应的 j 使得 j xor P 是 k xor 2^M,连向 j 即可。
#include <cstdio>
int main() {
int N, M; scanf("%d", &N);
for(M = 0; (M << 1 | 1) <= N; M = (M << 1 | 1));
if( N == 1 || M + 1 == N )
puts("No");
else {
puts("Yes");
int P = (M + 1) >> 1;
for(int i=1;i<P;i++) {
printf("%d %d\n", i + N + P, i);
printf("%d %d\n", i, P);
printf("%d %d\n", P, i + P);
printf("%d %d\n", i + P, i + N);
}
printf("%d %d\n", P + N, N + 1);
if( M != N ) {
printf("%d %d\n", P, M + 1);
printf("%d %d\n", M + 1 + N, M + 2 + N);
for(int i=M+2;i<=N;i++) {
int x = i - (M + 1);
if( x < P ) printf("%d %d\n", i + N, x + P);
else if( x == P ) printf("%d %d\n", i + N, x);
else printf("%d %d\n", i + N, x - P);
printf("%d %d\n", M + 1, i);
}
}
}
}
poj - 1228:N 个点恰好组成一个凸包,首先需要这 N 个点在凸包上,其次需要加入任意一个点都会出现 > 180° 的角,等价于凸包上的任意两个相邻的点之间连的边,都在凸包上与一个 = 180° 的角相邻。注意特判 N 个点共线的情况(包括 N = 1, N = 2 等特殊情况)。
#include <cstdio>
#include <algorithm>
using namespace std;
const double INF = 1E9;
const int MAXN = 2000;
struct point{
double x, y;
point() : x(), y() {}
point(double _x, double _y) : x(_x), y(_y) {}
friend point operator - (point a, point b) {return point(a.x - b.x, a.y - b.y);}
friend bool operator < (point a, point b) {return (a.x == b.x ? a.y < b.y : a.x < b.x);}
friend double operator ^ (point a, point b) {return a.x*b.y - a.y*b.x;}
friend double slope(point a, point b) {
if( a.x == b.x )
return a.y < b.y ? INF : -INF;
else return (a.y - b.y) / (a.x - b.x);
}
friend void convex(point *A, int n, point *B, int &m) {
static point t[MAXN + 5], s[MAXN + 5];
for(int i=0;i<n;i++) t[i] = A[i]; sort(t, t + n);
int cnt = 0, tp = 0;
for(int i=0;i<n;i++) {
while( tp >= 2 && slope(s[tp - 1], s[tp]) > slope(s[tp], t[i]) )
tp--;
s[++tp] = t[i];
}
for(int i=1;i<=tp;i++) B[cnt++] = s[i];
tp = 0;
for(int i=0;i<n;i++) {
while( tp >= 2 && slope(s[tp - 1], s[tp]) < slope(s[tp], t[i]) )
tp--;
s[++tp] = t[i];
}
for(int i=tp-1;i>=2;i--) B[cnt++] = s[i];
m = cnt;
}
friend void read(point &A) {
scanf("%lf%lf", &A.x, &A.y);
}
};
point p[MAXN + 5];
void solve() {
int n; scanf("%d", &n);
for(int i=0;i<n;i++) read(p[i]);
int m; convex(p, n, p, m);
if( n == 1 || n == 2 || n != m )
puts("NO");
else {
int cnt = 1; p[n] = p[0], p[n+1] = p[1];
for(int i=2;i<=n+1;i++) {
if( (p[i] - p[i-1]) ^ (p[i-1] - p[i-2]) ) {
if( cnt == 1 ) {
puts("NO");
return ;
}
cnt = 1;
}
else cnt++;
}
puts("YES");
}
}
int main() {
int T; scanf("%d", &T);
while( T-- ) solve();
}
codeforces - 963E:直接高斯消元 O(R^6)。注意到网格图,那么当 |i - j| > 2*R 时 a[i][j] 恒等于 0。那么每一行消元只会用到 R*R 个元素,所以优化一下复杂度就是 O(R^4)。注意到对角线上的元素在消元过程中不可能变为 0(考虑方程的实际意义是 dp 转移式),所以不需要交换行。另外,本题需要消成上三角 + 回代。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MOD = int(1E9) + 7;
const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, -1, 0, 1};
#define lb(x) max(0, x - d)
#define rb(x) min(n - 1, x + d)
struct mint{
int x; mint(int _x=0) : x(_x) {}
friend mint operator + (mint a, mint b) {return a.x + b.x >= MOD ? a.x + b.x - MOD : a.x + b.x;}
friend mint operator - (mint a, mint b) {return a.x - b.x < 0 ? a.x - b.x + MOD : a.x - b.x;}
friend mint operator * (mint a, mint b) {return 1LL*a.x*b.x%MOD;}
friend mint pow(mint b, int p) {
mint ret = 1;
for(int i=p;i;i>>=1,b*=b)
if( i & 1 ) ret *= b;
return ret;
}
friend mint operator / (mint a, mint b) {return a*pow(b, MOD-2);}
friend void operator += (mint &a, mint b) {a = a + b;}
friend void operator -= (mint &a, mint b) {a = a - b;}
friend void operator *= (mint &a, mint b) {a = a * b;}
friend void operator /= (mint &a, mint b) {a = a / b;}
};
mint A[8000][8000];
void gauss(int n, int d) {
for(int i=0;i<n;i++) {
mint k = pow(A[i][i], MOD-2);
for(int j=i;j<=rb(i);j++)
A[i][j] *= k;
A[i][n] *= k;
for(int j=i+1;j<=rb(i);j++) {
k = A[j][i];
for(int p=i;p<=rb(i);p++)
A[j][p] -= k*A[i][p];
A[j][n] -= k*A[i][n];
}
}
for(int i=n-1;i>=0;i--)
for(int j=i-1;j>=0;j--)
A[j][n] -= A[i][n] * A[j][i];
}
int R; mint p[4], s;
int id[105][105], cnt;
int main() {
scanf("%d", &R);
for(int i=0;i<4;i++) scanf("%d", &p[i].x), s += p[i];
for(int i=0;i<4;i++) p[i] /= s;
int mx = 0;
for(int i=-R;i<=R;i++)
for(int j=-R;j<=R;j++)
if( i*i + j*j <= R*R )
id[i+R][j+R] = (cnt++);
for(int i=-R;i<=R;i++)
for(int j=-R;j<=R;j++) {
if( i*i + j*j > R*R ) continue;
int t1 = id[i+R][j+R];
for(int k=0;k<4;k++) {
int x0 = i + dx[k], y0 = j + dy[k];
if( x0*x0 + y0*y0 > R*R ) continue;
int t2 = id[x0+R][y0+R];
A[t1][t2] -= p[k], mx = max(mx, abs(t1 - t2));
}
A[t1][t1] = A[t1][cnt] = 1;
}
gauss(cnt, mx);
printf("%d\n", A[id[R][R]][cnt].x);
}
bzoj - 1152:概率生成函数模板题。详见论文《浅谈生成函数在掷骰子问题上的应用》(杨懋龙)。推出来 $ans = \sum_{i}n^i*[字符串有长度为 i 的 border]$,用 kmp 即可,时间复杂度 O(n)。
#include <cstdio>
const int MAXN = 100000;
const int MOD = int(1E4);
int pw[MAXN + 5];
int a[MAXN + 5], f[MAXN + 5], n, t;
void solve() {
int m; scanf("%d", &m);
for(int i=1;i<=m;i++)
scanf("%d", &a[i]);
f[0] = -1, f[1] = 0;
for(int i=2;i<=m;i++) {
f[i] = f[i-1];
while( f[i] != -1 && a[f[i]+1] != a[i] )
f[i] = f[f[i]];
f[i]++;
}
int ans = 0;
for(int p=m;p;p=f[p])
ans = (ans + pw[p])%MOD;
printf("%04d\n", ans);
}
int main() {
scanf("%d%d", &n, &t);
pw[0] = 1;
for(int i=1;i<=MAXN;i++)
pw[i] = pw[i-1]*n%MOD;
while( t-- ) solve();
}
codefoces - 235D:与 bzoj3451 挺类似的,只是把树换成了基环树。一样的思路做即可(还不用 fft)。
#include <cstdio>
const int MAXN = 3000;
struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
#define rep(x) for(edge *p=adj[x];p;p=p->nxt)
bool vis[MAXN + 5], tag[MAXN + 5];
int a[MAXN + 5], cnt, siz;
void dfs1(int x, int f) {
vis[a[++cnt] = x] = true;
rep(x) {
if( p->to == f ) continue;
if( vis[p->to] ) {
siz = 0;
for(int j=cnt;;j--) {
tag[a[j]] = true, siz++;
if( a[j] == p->to ) break;
}
}
else dfs1(p->to, x);
if( siz ) break;
}
a[cnt--] = 0, vis[x] = false;
}
double ans;
double get(int s, int d) {
if( s >= 2 ) {
int p = s - 2, q = siz - s, r = d - s + 2;
return 1.0 / (r + p) + 1.0 / (r + q) - 1.0 / (r + p + q);
}
else return 1.0 / d;
}
void dfs2(int x, int s, int d) {
vis[x] = true, d++;
if( tag[x] ) s++;
ans += get(s, d);
rep(x) {
if( vis[p->to] ) continue;
dfs2(p->to, s, d);
}
}
int main() {
int n; scanf("%d", &n);
for(int i=1;i<=n;i++) {
int a, b; scanf("%d%d", &a, &b);
addedge(a + 1, b + 1);
}
dfs1(1, 0);
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) vis[j] = false;
dfs2(i, 0, 0);
}
printf("%.9f\n", ans);
}
codeforces - 457D:考虑一个覆盖 x 行 y 列的方案中 2^(x+y) 的组合意义:所有子集。反过来,这样一个方案会在其他包含它的方案中被统计,那么除这 x 行 y 列空余位置随便填数即可。剩下的就很简单了。注意合理利用对数运算计算组合数与比较大小。
#include <cmath>
#include <cstdio>
#include <iomanip>
#include <iostream>
using namespace std;
const int MAXM = 100000;
long double fct[MAXM + 5];
void init() {
for(int i=1;i<=MAXM;i++)
fct[i] = fct[i-1] + log10(i);
}
long double comb(int n, int m) {
return fct[n] - fct[m] - fct[n-m];
}
int main() {
init();
int n, m, k; scanf("%d%d%d", &n, &m, &k);
long double ans = 0;
for(int i=0;i<=n;i++) {
for(int j=0;j<=n;j++) {
int t = (i + j)*n - i*j;
if( k < t ) continue;
long double d = comb(m-t, k-t) - comb(m, k) + comb(n, i) + comb(n, j);
ans += pow(10, d);
if( d > 99 ) {
printf("1e99");
return 0;
}
}
}
if( log10(ans) > 99 ) printf("1e99");
else cout << fixed << setprecision(9) << ans;
}
topcoder - SRM686D1L2:答案为 $\sum_{i=0}^{n}{n \brack i}i^m$,斯特林反演并交换求和得到 $\sum_{j=0}^{m}{m \brace j}j!(\sum_{i=0}^{n}{n \brack i}{n \choose j})$,考虑 $\sum_{i=0}^{n}{n \brack i}{n \choose j}$ 的组合意义发现它等于 $\sum_{i=0}^{n}{i \brack j}{n \choose i}(n-i)!$。O(nm) 预处理即可。
#include <cstdio>
#include <vector>
using namespace std;
const int MOD = int(1E9) + 7;
const int MAXM = 300;
const int MAXN = 100000;
int add(int x, int y) {return (x + y >= MOD ? x + y - MOD : x + y);}
int sub(int x, int y) {return (x - y < 0 ? x - y + MOD : x - y);}
int mul(int x, int y) {return 1LL*x*y%MOD;}
int pow_mod(int b, int p) {
int ret = 1;
for(int i=p;i;i>>=1,b=mul(b,b))
if( i & 1 ) ret = mul(ret, b);
return ret;
}
int s1[MAXN + 5][MAXM + 5], s2[MAXM + 5][MAXM + 5];
int fct[MAXN + 5], ifct[MAXN + 5];
void init() {
fct[0] = 1;
for(int i=1;i<=MAXN;i++) fct[i] = mul(fct[i-1], i);
ifct[MAXN] = pow_mod(fct[MAXN], MOD - 2);
for(int i=MAXN-1;i>=0;i--)
ifct[i] = mul(ifct[i+1], i+1);
s1[0][0] = 1;
for(int i=1;i<=MAXN;i++)
for(int j=1;j<=MAXM;j++)
s1[i][j] = add(s1[i-1][j-1], mul(s1[i-1][j], i-1));
for(int j=0;j<=MAXM;j++)
for(int i=1;i<=MAXN;i++)
s1[i][j] = add(s1[i-1][j], mul(s1[i][j], ifct[i]));
s2[0][0] = 1;
for(int i=1;i<=MAXM;i++)
for(int j=1;j<=MAXM;j++)
s2[i][j] = add(s2[i-1][j-1], mul(s2[i-1][j], j));
}
class CyclesNumber{
public :
int get(int n, int m) {
int ret = 0;
for(int j=0;j<=m;j++)
ret = add(ret, mul(s1[n][j], mul(s2[m][j], fct[j])));
return mul(ret, fct[n]);
}
vector<int>getExpectation(vector<int>n, vector<int>m) {
init(); vector<int>ans;
for(int i=0;i<n.size();i++)
ans.push_back(get(n[i], m[i]));
return ans;
}
};
atcoder - AGC030C:K <= 500 时直接一行一种颜色,否则构造 500*500 的方阵,按对角线涂颜色。保证每条对角线(模意义下)要么是一种颜色,要么是两种颜色交替出现,这样就可以构造 [500, 2*500] 内的所有数字。
#include <cstdio>
const int N = 500;
int a[N][N];
int main() {
int K; scanf("%d", &K);
if( K <= N ) {
printf("%d\n", K);
for(int i=1;i<=K;i++) {
for(int j=1;j<=K;j++)
printf("%d%c", i, (j == K ? '\n' : ' '));
}
}
else {
printf("%d\n", N);
int cnt = 0;
for(int i=0;i<2*N-K;i++) {
int x = 0, y = i, b = (++cnt);
do {
a[x][y] = b;
x = (x + 1 == N ? 0 : x + 1);
y = (y + 1 == N ? 0 : y + 1);
}while( x );
}
for(int i=2*N-K;i<N;i++) {
int x = 0, y = i, b[2] = {};
b[0] = (++cnt), b[1] = (++cnt);
do {
a[x][y] = b[x & 1];
x = (x + 1 == N ? 0 : x + 1);
y = (y + 1 == N ? 0 : y + 1);
}while( x );
}
for(int i=0;i<N;i++) {
for(int j=0;j<N;j++)
printf("%d%c", a[i][j], (j + 1 == N ? '\n' : ' '));
}
}
}
codeforces - 1236F:连通块个数 = 点的个数 - 边的个数 + 环的个数。把方差拆成 E(x^2) - E^2(x) 的形式,然后就是个套路的平方期望。讨论一下即可。
#include <cstdio>
#include <vector>
using namespace std;
const int MAXN = 500000;
const int MOD = int(1E9) + 7;
struct mint{
int x; mint(int _x=0) : x(_x) {}
friend mint operator + (mint a, mint b) {return a.x + b.x >= MOD ? a.x + b.x - MOD : a.x + b.x;}
friend mint operator - (mint a, mint b) {return a.x - b.x < 0 ? a.x - b.x + MOD : a.x - b.x;}
friend mint operator * (mint a, mint b) {return 1LL*a.x*b.x%MOD;}
friend void operator += (mint &a, mint b) {a = a + b;}
friend void operator -= (mint &a, mint b) {a = a - b;}
friend void operator *= (mint &a, mint b) {a = a * b;}
};
const mint INV2 = (MOD + 1) >> 1;
mint ipw2[MAXN + 10];
void init() {
ipw2[0] = 1;
for(int i=1;i<=MAXN+5;i++)
ipw2[i] = ipw2[i-1]*INV2;
}
struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
mint sum[MAXN + 5], deg[MAXN + 5], siz[MAXN + 5], all;
vector<int>v[MAXN + 5];
int n, m, tot;
int dfn[MAXN + 5], stk[MAXN + 5], dcnt, tp;
void dfs(int x, int f) {
dfn[x] = (++dcnt), stk[++tp] = x;
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
if( !dfn[p->to] ) dfs(p->to, x);
else if( dfn[p->to] < dfn[x] ) {
tot++;
for(int i=tp;;i--) {
v[tot].push_back(stk[i]);
if( stk[i] == p->to ) break;
}
siz[tot] = v[tot].size(), all += ipw2[v[tot].size()];
for(int i=0;i<v[tot].size();i++)
sum[v[tot][i]] += ipw2[v[tot].size()];
}
}
stk[tp--] = 0;
}
mint get1() {
mint ret = 0, cnt = 1LL*m*(m-1)%MOD;
for(int i=1;i<=n;i++) {
ret += ipw2[1] + (n - 1)*ipw2[2]; // V * V
ret -= (deg[i]*ipw2[2] + (m - deg[i])*ipw2[3])*2; // V * E
ret += deg[i]*(deg[i] - 1)*ipw2[3]; // E * E
cnt -= deg[i]*(deg[i]-1);
}
ret += cnt*ipw2[4] + m*ipw2[2]; // E * E
for(int i=1;i<=tot;i++) {
int s = v[i].size();
ret += (siz[i]*ipw2[s] + (n - siz[i])*ipw2[s+1])*2; // V * F
mint p = m - s, q = all - ipw2[s];
for(int j=0;j<v[i].size();j++) {
int x = v[i][j];
ret -= (deg[x] - 2)*ipw2[s+1]*2; // E * F
p -= deg[x] - 2;
ret += (sum[x] - ipw2[s])*ipw2[s-1]; // F * F
q -= (sum[x] - ipw2[s]);
}
ret -= (s*ipw2[s] + p*ipw2[s+2])*2; // E * F
ret += ipw2[s] + q*ipw2[s]; // F * F
}
return ret;
}
mint get2() {
mint ret = 0;
ret += n*ipw2[1]; // V
ret -= m*ipw2[2]; // E
for(int i=1;i<=tot;i++)
ret += ipw2[v[i].size()]; // F
return ret;
}
int main() {
init(); scanf("%d%d", &n, &m);
for(int i=1;i<=m;i++) {
int u, v; scanf("%d%d", &u, &v);
addedge(u, v), deg[u] += 1, deg[v] += 1;
}
dfs(1, 0);
mint x = get1(), y = get2();
printf("%d\n", (x - y*y).x);
}
hdu - 4652:可以看成长度为 n,字符集大小为 m 的全相同/全不同字符串为终止状态的抛骰子期望次数。可以使用概率生成函数来解。因为这些字符串是对称的,所以它们的生成函数也是一样的。推出相同时为 $\sum_{i=0}^{n-1}m^i$,不同时为 $\sum_{i=1}^{n}\frac{m^i}{m^{\underline{i}}}$。可以 O(n) 算。
#include <cstdio>
#include <cmath>
using namespace std;
void solve() {
int op; scanf("%d", &op);
int m, n; scanf("%d%d", &m, &n);
if( op == 0 ) {
printf("%.9f\n", (m == 1 ? n : (pow(m,n)-1)/(m-1)));
}
else {
double ans = 1;
for(int i=n-1;i>=1;i--)
ans = 1 + ans * m / (m - i);
printf("%.9f\n", ans);
}
}
int main() {
int T; scanf("%d", &T);
while( T-- ) solve();
}
codeforces - 611G:旋转卡壳找经过某个点,能够将多边形面积进行尽可能均等划分的分界点。维护一下相邻点叉积、横纵坐标和即可。注意取模的问题,有些地方要比较大小所以不能取模。
#include <cstdio>
typedef long long ll;
const int MAXN = 500000;
const int MOD = int(1E9) + 7;
struct point{
ll x, y; point() {}
point(ll _x, ll _y) : x(_x), y(_y) {}
friend point operator - (point a, point b) {return point(a.x - b.x, a.y - b.y);}
friend ll operator ^ (point a, point b) {return a.x*b.y - a.y*b.x;}
friend void operator -= (point &a, point b) {a = a - b;}
}p[MAXN + 5];
ll S; int n;
int nxt(int x) {return x + 1 == n ? 0 : x + 1;}
int lst(int x) {return x == 0 ? n - 1 : x - 1;}
int main() {
scanf("%d", &n);
for(int i=0;i<n;i++) scanf("%lld%lld", &p[i].x, &p[i].y);
for(int i=n-1;i>=0;i--) p[i] -= p[0];
for(int i=0;i<n;i++) S += p[nxt(i)]^p[i];
ll t1 = 0; int j = n - 1;
int ans = 0, t2 = 0, cnt = 0, sx = 0, sy = 0;
for(int i=0;i<n;i++) {
while( true ) {
int k = nxt(j);
ll s = t1 + (p[k]^p[j]) + (p[i]^p[k]);
if( S - s < s ) break;
t1 += (p[k]^p[j]), sx = (sx + p[k].x) % MOD, sy = (sy + p[k].y) % MOD;
t2 = (t2 + t1%MOD) % MOD, cnt++, j = k;
}
ans = (ans - 2LL*t2%MOD)%MOD; // ans -= 2*t2;
ans = (ans - 2*(p[i].x*sy - p[i].y*sx)%MOD)%MOD; // ans -= 2*(p[i].x*sy - p[i].y*sx);
ans = (ans + (cnt-2)*(S%MOD)%MOD)%MOD; // ans += (cnt-2)*S;
t1 -= (p[nxt(i)]^p[i]), sx = (sx - p[i].x) % MOD, sy = (sy - p[i].y) % MOD;
t2 = (t2 - (cnt-1)*((p[nxt(i)]^p[i])%MOD)%MOD)%MOD, cnt--;
}
printf("%d\n", (ans + MOD) % MOD);
}
codeforces - 1278F:可以考虑每个可能性的概率乘贡献,然后斯特林反演;也可以直接展开 E(X^k)=E((x1+x2+...+xn)^k),考虑每一项的贡献。最后推出 $ans = \sum_{p=0}^{k} \frac{{k\brace p}\times p! \times {n\choose p}}{m^p}$。
#include <cstdio>
const int MOD = 998244353;
const int MAXN = 5000;
int pow_mod(int b, int p) {
int ret = 1;
for(int i=p;i;i>>=1,b=1LL*b*b%MOD)
if( i & 1 ) ret = 1LL*ret*b%MOD;
return ret;
}
int s[MAXN + 5][MAXN + 5], c[MAXN + 5], fct[MAXN + 5], ipwm[MAXN + 5];
int n, m, k;
void init() {
int im = pow_mod(m, MOD - 2);
ipwm[0] = fct[0] = s[0][0] = c[0] = 1;
for(int i=1;i<=k;i++) {
ipwm[i] = 1LL*ipwm[i-1]*im%MOD;
c[i] = 1LL*c[i-1]*(n-i+1)%MOD*pow_mod(i, MOD-2)%MOD;
fct[i] = 1LL*fct[i-1]*i%MOD;
for(int j=1;j<=k;j++)
s[i][j] = (s[i-1][j-1] + 1LL*j*s[i-1][j]%MOD)%MOD;
}
}
int main() {
scanf("%d%d%d", &n, &m, &k), init();
int ans = 0;
for(int i=0;i<=k;i++)
ans = (ans + 1LL*s[k][i]*fct[i]%MOD*c[i]%MOD*ipwm[i]%MOD)%MOD;
printf("%d\n", ans);
}
atcoder - ARC093E:最终要么是最小生成树(最小生成树不涂成相同颜色),要么是最小生成树换掉一条边(最小生成树涂成相同颜色)。然后随便做。
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1000;
const int MAXM = 2000;
const int MOD = int(1E9) + 7;
int pw2[MAXM + 5];
void init() {
pw2[0] = 1;
for(int i=1;i<=MAXM;i++)
pw2[i] = 2LL*pw2[i-1]%MOD;
}
struct edge{
int u, v; ll w;
friend bool operator < (const edge &a, const edge &b) {
return a.w < b.w;
}
}e[MAXM + 5], t[MAXM + 5];
ll G[MAXN + 5][MAXN + 5];
int fa[MAXN + 5];
vector<int>v[MAXN + 5];
int find(int x) {
return (x == fa[x] ? x : find(fa[x]));
}
bool merge(int x, int y, ll w) {
if( find(x) == find(y) ) return false;
x = find(x), y = find(y);
if( v[x].size() > v[y].size() ) swap(x, y);
for(int i=0;i<v[x].size();i++)
for(int j=0;j<v[y].size();j++)
G[v[x][i]][v[y][j]] = G[v[y][j]][v[x][i]] = w;
fa[x] = y;
for(int i=0;i<v[x].size();i++)
v[y].push_back(v[x][i]);
return true;
}
int main() {
init();
int N, M; ll X;
scanf("%d%d%lld", &N, &M, &X);
for(int i=1;i<=M;i++)
scanf("%d%d%lld", &e[i].u, &e[i].v, &e[i].w);
sort(e + 1, e + M + 1);
for(int i=1;i<=N;i++) fa[i] = i, v[i].push_back(i);
ll A = 0; int cnt = 0;
for(int i=1;i<=M;i++) {
if( !merge(e[i].u, e[i].v, e[i].w) )
t[++cnt] = e[i], t[cnt].w -= G[e[i].u][e[i].v];
else A += e[i].w;
}
int ans = 0;
if( A == X ) ans = (pw2[M] + MOD - pw2[M-N+1+1]) % MOD;
sort(t + 1, t + cnt + 1);
for(int i=1;i<=cnt;i++)
if( A + t[i].w == X ) ans = (ans + pw2[cnt-i+1]) % MOD;
printf("%d\n", ans);
}
atcoder - ARC103D:如果存在两个点 X+Y 奇偶性不同则无解。否则,我们可以构造 {1, 2, 4, ...} (如果 X+Y 为偶数则多一个 1),每次走第 2^i 步时,总是保证 X,Y 一个模 2^(i+2) 为 0,一个模 2^(i+2) 为 2^(i+1) 即可。这样到最后一步 X,Y 一定一个为 0,一个为 2^m,直接走即可。
#include <cstdio>
typedef long long ll;
const int MAXN = 1000;
ll X[MAXN + 5], Y[MAXN + 5]; int N;
ll abs(ll x) {
return x >= 0 ? x : -x;
}
int main() {
scanf("%d", &N);
for(int i=1;i<=N;i++)
scanf("%lld%lld", &X[i], &Y[i]);
int type = ((X[1] + Y[1]) & 1);
for(int i=1;i<=N;i++) {
if( type != ((X[i] + Y[i]) & 1) ) {
type = -1;
break;
}
}
if( type == -1 ) {
puts("-1");
return 0;
}
else if( type == 0 ) {
printf("%d\n%d", 40, 1);
for(int i=0;i<39;i++)
printf(" %lld", (1LL<<i));
puts("");
for(int i=1;i<=N;i++) {
putchar('L'), X[i]++;
for(int j=0;j<38;j++) {
ll t1 = (1LL << j), t2 = (1LL << (j + 1)), t3 = (1LL << (j + 2));
if( abs(X[i] % t2) == t1 ) {
if( abs(Y[i] % t3) + abs((X[i] + t1) % t3) == t2 )
putchar('L'), X[i] += t1;
else putchar('R'), X[i] -= t1;
}
else {
if( abs(X[i] % t3) + abs((Y[i] + t1) % t3) == t2 )
putchar('D'), Y[i] += t1;
else putchar('U'), Y[i] -= t1;
}
}
ll t1 = (1LL << 38), t2 = (1LL << 39), t3 = (1LL << 40);
if( abs(X[i] % t2) == t1 ) {
if( abs(Y[i] % t3) + abs((X[i] + t1) % t3) == t2 )
puts("R"), X[i] -= t1;
else puts("L"), X[i] += t1;
}
else {
if( abs(X[i] % t3) + abs((Y[i] + t1) % t3) == t2 )
puts("U"), Y[i] -= t1;
else puts("D"), Y[i] += t1;
}
}
}
else {
printf("%d\n", 40);
for(int i=0;i<40;i++)
printf("%lld ", (1LL<<i));
puts("");
for(int i=1;i<=N;i++) {
for(int j=0;j<39;j++) {
ll t1 = (1LL << j), t2 = (1LL << (j + 1)), t3 = (1LL << (j + 2));
if( abs(X[i] % t2) == t1 ) {
if( abs(Y[i] % t3) + abs((X[i] + t1) % t3) == t2 )
putchar('L'), X[i] += t1;
else putchar('R'), X[i] -= t1;
}
else {
if( abs(X[i] % t3) + abs((Y[i] + t1) % t3) == t2 )
putchar('D'), Y[i] += t1;
else putchar('U'), Y[i] -= t1;
}
}
ll t1 = (1LL << 39), t2 = (1LL << 40), t3 = (1LL << 41);
if( abs(X[i] % t2) == t1 ) {
if( abs(Y[i] % t3) + abs((X[i] + t1) % t3) == t2 )
puts("R"), X[i] -= t1;
else puts("L"), X[i] += t1;
}
else {
if( abs(X[i] % t3) + abs((Y[i] + t1) % t3) == t2 )
puts("U"), Y[i] -= t1;
else puts("D"), Y[i] += t1;
}
}
}
}
bzoj - 4878:我们可以通过取一个点相邻的已被染色的点的颜色集合的 mex 进行图染色,则颜色 i 的点必然连向颜色 0~i-1 的点。假如最大颜色 >= k,则必然存在一条 0~k-1 的路径,直接输出这个路径;否则输出染色方案。
#include <set>
#include <cstdio>
using namespace std;
const int MAXN = 1000;
const int MAXM = 10000;
struct edge{
edge *nxt; int to;
}edges[2*MAXM + 5], *adj[MAXN + 5], *ecnt;
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
set<int>st;
int tg[MAXN + 5], pre[MAXN + 5], clr[MAXN + 5];
bool flag; int n, m, k;
void clear() {
flag = true, ecnt = edges;
for(int i=1;i<=n;i++)
clr[i] = -1, adj[i] = NULL;
st.clear();
for(int i=0;i<k;i++)
st.insert(i), tg[i] = -1;
}
void print(int x) {
if( clr[x] == 0 ) {
printf("path");
}
else print(pre[x]);
printf(" %d", x);
}
void dfs(int x) {
for(edge *p=adj[x];p;p=p->nxt)
if( clr[p->to] != -1 ) st.erase(clr[p->to]), tg[clr[p->to]] = p->to;
if( !st.size() ) {
flag = false;
clr[x] = k, pre[x] = tg[clr[x]-1];
print(x), puts("");
return ;
}
else clr[x] = *st.begin(), pre[x] = tg[clr[x]-1];
for(edge *p=adj[x];p;p=p->nxt)
if( clr[p->to] != -1 ) st.insert(clr[p->to]), tg[clr[p->to]] = -1;
for(edge *p=adj[x];p;p=p->nxt)
if( clr[p->to] == -1 ) {
dfs(p->to);
if( !flag ) return ;
}
}
void solve() {
scanf("%d%d%d", &n, &m, &k), clear();
for(int i=1;i<=m;i++) {
int a, b; scanf("%d%d", &a, &b);
addedge(a, b);
}
for(int i=1;i<=n;i++)
if( clr[i] == -1 ) {
dfs(i);
if( !flag ) return ;
}
if( flag ) {
printf("color");
for(int i=1;i<=n;i++)
printf(" %d", clr[i] + 1);
puts("");
}
}
int main() {
int T; scanf("%d", &T);
while( T-- ) solve();
}
atcoder - ARC080F:可以先差分,区间翻转变为两点翻转。两个相距为奇素数的点一起翻转代价为 1,相距为偶数代价为 2,否则代价为 3(哥德巴赫猜想)。因此将点按奇偶分类,相距奇素数的先跑二分图匹配,剩下的奇偶内部匹配,实在不行补一个代价为 3 的匹配。
#include <vector>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = int(1E7) + 5;
const int MAXM = 100;
bool nprm[MAXN + 5];
int prm[MAXN + 5], pcnt;
void init() {
nprm[1] = true;
for(int i=2;i<=MAXN;i++) {
if( !nprm[i] ) prm[++pcnt] = i;
for(int j=1;prm[j]<=MAXN/i;j++) {
nprm[i*prm[j]] = true;
if( i % prm[j] == 0 ) break;
}
}
}
int a[MAXN + 5], b[2][MAXM + 5], cnt[2];
int G[MAXM + 5][MAXM + 5], lnk[MAXM + 5];
bool vis[MAXM + 5];
bool dfs(int x) {
for(int i=1;i<=cnt[1];i++) {
if( !vis[i] && G[x][i] ) {
vis[i] = true;
if( !lnk[i] || dfs(lnk[i]) ) {
lnk[i] = x;
return true;
}
}
}
return false;
}
int abs(int x) {return x >= 0 ? x : -x;}
int main() {
init();
int N; scanf("%d", &N);
for(int i=1;i<=N;i++) {
int x; scanf("%d", &x);
a[x] = 1;
}
for(int i=1;i<=MAXN;i++)
if( a[i] != a[i-1] ) b[i%2][++cnt[i%2]] = i;
int ans = 0;
for(int i=1;i<=cnt[0];i++)
for(int j=1;j<=cnt[1];j++)
G[i][j] = (!nprm[abs(b[0][i] - b[1][j])]);
for(int i=1;i<=cnt[0];i++) {
for(int j=1;j<=cnt[1];j++) vis[j] = false;
if( dfs(i) ) ans++;
}
ans += (cnt[0] - ans)/2*2 + (cnt[1] - ans)/2*2;
if( (cnt[0] - ans) & 1 ) ans += 3;
printf("%d\n", ans);
}
topcoder - SRM553D1L3:考虑如果已知环长 c,可以根据前缀和建出差分约束。对于合法的 c,首先最大前缀和 < c(因此建最长路求最小值),其次不能有正环。因为简单路径/环的长度为 O(n),所以考虑对于每个 k,求权值形如 k*c + b 的最长路径/环,解不等式得 c 的范围。
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
const ll INF = (1LL << 60);
class YamanoteLine{
private:
ll G[101][50][50]; int n;
void init() {
for(int i=-n;i<=n;i++)
for(int j=0;j<n;j++)
for(int k=0;k<n;k++)
G[i+n][j][k] = (j == k && i == 0 ? 0 : -INF);
}
void addedge(int u, int v, ll d, int w) {
G[w+n][u][v] = max(G[w+n][u][v], d);
// printf("%d %d : %lld %d\n", u, v, d, w);
}
void floyd() {
for(int k=0;k<n;k++)
for(int p=-n;p<=n;p++)
for(int q=-n;q<=n;q++) {
if( -n > p + q || p + q > n ) continue;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
G[p+q+n][i][j] = max(G[p+q+n][i][j], G[p+n][i][k] + G[q+n][k][j]);
}
}
public:
ll howMany(int _n, vi s1, vi t1, vi l1, vi s2, vi t2, vi l2) {
n = _n; init();
for(int i=1;i<n;i++)
addedge(i-1, i, 1, 0);
for(int i=0;i<(int)s1.size();i++) {
if( s1[i] < t1[i] )
addedge(s1[i], t1[i], l1[i], 0);
else addedge(s1[i], t1[i], l1[i], -1);
}
for(int i=0;i<(int)s2.size();i++) {
if( s2[i] < t2[i] )
addedge(t2[i], s2[i], -l2[i], 0);
else addedge(t2[i], s2[i], -l2[i], 1);
}
floyd();
ll lb = -INF, ub = INF;
for(int k=-n;k<=n;k++) {
ll b = G[k+n][0][n-1];
if( k - 1 == 0 ) {
if( 0 > -b-1 ) return 0;
}
else if( k - 1 < 0 )
lb = max(lb, (ll)ceil(1.0*(-b-1)/(k-1)));
else if( k - 1 > 0 )
ub = min(ub, (ll)floor(1.0*(-b-1)/(k-1)));
}
for(int p=-n;p<=n;p++)
for(int q=-n;q<=n;q++)
for(int i=0;i<n;i++)
for(int j=0;j<n;j++) {
int k = p + q; ll b = G[p+n][i][j] + G[q+n][j][i];
if( k == 0 ) {
if( 0 > -b )
return 0;
}
else if( k < 0 )
lb = max(lb, (ll)ceil(-1.0*b/k));
else if( k > 0 )
ub = min(ub, (ll)floor(-1.0*b/k));
}
if( lb > ub ) return 0;
else return (ub - lb + 1 >= 1E15) ? -1 : ub - lb + 1;
}
};
hdu - 5608:杜教筛模板题,不过预处理需要容斥预处理。这个题说明杜教筛对积性函数并非强制要求,积性函数只是方便预处理。
#include <cstdio>
const int MOD = 1E9 + 7;
const int MAXN = 1000000;
const int INV2 = (MOD + 1) / 2;
const int INV6 = (MOD + 1) / 6;
int f[MAXN + 5];
void init() {
for(int i=1;i<=MAXN;i++)
f[i] = (1LL*i*i - 3*i + 2) % MOD;
for(int i=1;i<=MAXN;i++)
for(int j=2*i;j<=MAXN;j+=i)
f[j] = (f[j] + MOD - f[i]) % MOD;
for(int i=1;i<=MAXN;i++)
f[i] = (f[i-1] + f[i]) % MOD;
}
int get(int n) {
int ret = 1LL*n*(n+1)%MOD*(2*n%MOD+1)%MOD*INV6%MOD;
ret = (ret + MOD - 3LL*n*(n+1)/2%MOD) % MOD;
ret = (ret + 2LL*n%MOD)%MOD;
return ret;
}
int g[MAXN + 5], N;
int sum(int x) {
if( x <= MAXN ) return f[x];
else if( g[N/x] != -1 ) return g[N/x];
else {
int ret = get(x);
for(int i=2;i<=x;i++) {
int j = x/(x/i);
ret = (ret + MOD - 1LL*sum(x/i)*(j-i+1)%MOD)%MOD;
i = j;
}
return g[N/x] = ret;
}
}
void clear() {
for(int i=1;i<=N;i++) {
int j = N/(N/i);
if( j <= MAXN ) g[j] = -1;
i = j;
}
}
void solve() {
scanf("%d", &N), clear();
printf("%d\n", sum(N));
}
int main() {
init();
int T; scanf("%d", &T);
while( T-- ) solve();
}
codeforces - 588F:二分答案 + 2-sat 判定,优化一下建图。
#include <map>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 50000;
const int MAXV = 6*MAXN;
const int MAXE = 20*MAXN;
struct Graph{
struct edge{
int to; edge *nxt;
}edges[MAXE + 5], *adj[MAXV + 5], *ecnt;
Graph() {ecnt = edges;}
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
// printf("! %d %d \t (%d %d)\n", u, v, u/2, v/2);
}
}G1, G2;
#define ILLEGAL puts("No"), exit(0)
#define rep(G, x) for(Graph::edge *p=G.adj[x];p;p=p->nxt)
int n, m, cnt;
struct edge{
int u, v, c, t, id; edge() {}
edge(int _u, int _v, int _c, int _t, int _id) : u(_u), v(_v), c(_c), t(_t), id(_id) {}
}e[MAXN + 5];
bool cmp(const edge &a, const edge &b) {return a.t < b.t;}
int dfn[MAXV + 5], low[MAXV + 5], id[MAXV + 5];
int stk[MAXV + 5], tp, dcnt, tot;
void dfs(int x) {
dfn[x] = low[x] = (++dcnt), stk[++tp] = x;
rep(G2, x) {
if( dfn[p->to] ) {
if( !id[p->to] ) low[x] = min(low[x], dfn[p->to]);
}
else dfs(p->to), low[x] = min(low[x], low[p->to]);
}
if( low[x] >= dfn[x] ) {
tot++;
do {
id[stk[tp]] = tot;
}while( stk[tp--] != x );
}
}
bool check(int x) {
G2 = G1;
for(int i=x+1;i<m;i++)
G2.addedge(e[i].id<<1|1, e[i].id<<1);
for(int i=0;i<cnt;i++) dfn[i] = low[i] = id[i] = 0;
dcnt = tot = 0;
for(int i=0;i<cnt;i++)
if( !dfn[i] ) dfs(i);
for(int i=0;i<m;i++) {
// printf("%d : %2d %2d\n", i, id[i<<1], id[i<<1|1]);
if( id[i<<1] == id[i<<1|1] ) return false;
}
return true;
}
vector<int>v1[MAXV + 5];
void dfs2(int x) {
dfn[x] = low[x] = (++dcnt), stk[++tp] = x;
rep(G2, x) {
if( dfn[p->to] ) {
if( !id[p->to] ) low[x] = min(low[x], dfn[p->to]);
}
else dfs2(p->to), low[x] = min(low[x], low[p->to]);
}
if( low[x] >= dfn[x] ) {
tot++;
do {
v1[tot].push_back(stk[tp]);
id[stk[tp]] = tot;
}while( stk[tp--] != x );
}
}
bool tag[MAXV + 5]; vector<int>ans;
void print(int x) {
G2 = G1;
for(int i=x+1;i<m;i++)
G2.addedge(e[i].id<<1|1, e[i].id<<1);
for(int i=0;i<cnt;i++) dfn[i] = low[i] = id[i] = 0;
dcnt = tot = 0;
for(int i=0;i<cnt;i++)
if( !dfn[i] ) dfs2(i);
for(int i=1;i<=tot;i++) {
for(int j=0;j<(int)v1[i].size();j++) {
int x = v1[i][j];
rep(G2, x)
if( tag[id[p->to]] ) tag[i] = true;
}
if( !tag[i] ) {
for(int j=0;j<(int)v1[i].size();j++)
if( v1[i][j] < 2*m ) tag[id[v1[i][j]^1]] = true;
}
}
for(int i=0;i<m;i++) {
if( tag[id[i<<1]] ) ans.push_back(i + 1);
// printf("%d : %d %d\n", i, tag[id[i<<1]], tag[id[i<<1|1]]);
}
printf("%d\n", ans.size());
for(int i=0;i<(int)ans.size();i++)
printf("%d ", ans[i]);
}
map<int, int>mp;
vector<edge>vec[MAXN + 5];
int main() {
scanf("%d%d", &n, &m);
for(int i=0;i<m;i++) {
int u, v, c, t; scanf("%d%d%d%d", &u, &v, &c, &t);
e[i] = edge(u, v, c, t, i);
vec[u].push_back(e[i]), vec[v].push_back(e[i]);
}
cnt = 2*m;
for(int i=1;i<=n;i++) {
bool flag = false; mp.clear();
for(int j=0;j<(int)vec[i].size();j++) {
if( mp.count(vec[i][j].c) ) {
if( flag ) ILLEGAL;
else flag = true;
int x = vec[i][j].id, y = mp[vec[i][j].c];
G1.addedge(x<<1, y<<1|1), G1.addedge(y<<1, x<<1|1);
}
else mp[vec[i][j].c] = vec[i][j].id;
}
if( vec[i].size() >= 2 ) {
int lst = (vec[i][0].id<<1);
for(int j=1;j<(int)vec[i].size();j++) {
int x = vec[i][j].id;
G1.addedge(x<<1|1, lst);
if( j + 1 == (int)vec[i].size() ) break;
G1.addedge(cnt, x<<1), G1.addedge(cnt, lst);
lst = cnt, cnt++;
}
lst = (vec[i][vec[i].size() - 1].id<<1);
for(int j=(int)vec[i].size()-2;j>=0;j--) {
int x = vec[i][j].id;
G1.addedge(x<<1|1, lst);
if( j == 0 ) break;
G1.addedge(cnt, x<<1), G1.addedge(cnt, lst);
lst = cnt, cnt++;
}
}
}
sort(e, e + m, cmp);
if( !check(m - 1) ) ILLEGAL;
else puts("Yes");
int le = -1, ri = m - 1;
while( le < ri ) {
int mid = (le + ri) >> 1;
if( check(mid) ) ri = mid;
else le = mid + 1;
}
printf("%d ", le == -1 ? 0 : e[le].t), print(le);
}
来源:oschina
链接:https://my.oschina.net/u/4308120/blog/3321003