LOJ刷题记录:2000-2005(SDOI2017)
立一个flag…loj不跳题刷题233
loj2000. 「SDOI2017」数字表格
求:
考虑枚举 gcd(i,j) 则原式为:
因此可以下底函数分块求。注意如果要对指数取模应该取 φ(Mod)=Mod−1 。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+5;
int T, n, m;
const int mod = 1e9+7, phimod = mod-1;
int f[MAXN], pf[MAXN], prime[MAXN], not_prime[MAXN], mu[MAXN], smu[MAXN], top = 0;
int inv_pf[MAXN];
int power(int a, int n, int mod)
{
int ans = 1;
for (int i = 0; i <= 30; i++) {
if (n&(1<<i)) ans = (long long)ans*a%mod;
a = (long long)a*a%mod;
}
return ans;
}
void get_prime(int N)
{
mu[1] = 1;
for (int i = 2; i <= N; i++) {
if (!not_prime[i]) prime[++top] = i, mu[i] = -1;
for (int j = 1; j <= top && i*prime[j] <= N; j++) {
not_prime[i*prime[j]] = 1;
if (i%prime[j] == 0) {
mu[i*prime[j]] = 0;
break;
}
mu[i*prime[j]] = -mu[i];
}
}
for (int i = 1; i <= N; i++) smu[i] = ((smu[i-1]+mu[i])%phimod+phimod)%phimod;
f[0] = 0, f[1] = 1;
for (int i = 2; i <= N; i++) f[i] = ((f[i-1]+f[i-2])%mod+mod)%mod;
pf[0] = 1;
for (int i = 1; i <= N; i++) pf[i] = (long long)pf[i-1]*f[i]%mod;
for (int i = 0; i <= N; i++) inv_pf[i] = power(pf[i], mod-2, mod);
}
int get_g(int d)
{
int N = n/d, M = m/d, ans = 0;
for (register int i = 1, last; i <= min(N, M); i = last+1) {
last = min(N/(N/i), M/(M/i));
ans = (ans+(long long)(smu[last]-smu[i-1])*(N/i)%phimod*(M/i)%phimod)%phimod;
}
return (ans+phimod)%phimod;
}
int main()
{
get_prime(1000000);
scanf("%d", &T);
for (int i = 1; i <= T; i++) {
scanf("%d%d", &n, &m);
int ans = 1;
for (register int i = 1, last; i <= min(n, m); i = last+1) {
last = min(n/(n/i), m/(m/i));
ans = (long long)ans*power((long long)pf[last]*inv_pf[i-1]%mod, get_g(i), mod)%mod;
}
printf("%d\n", ans);
}
return 0;
}
loj#2001. 「SDOI2017」树点涂色
比较神的一道数据结构…
首先树点染色操作就是lct的access…所以询问一个点到根的颜色数就是虚边个数+1;两点之间同理。因此可以用lct模拟染色,每次在换虚边的时候用dfs序+线段树维护每个点到根的虚边个数。就可以做询问了。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
struct seg_tree {
int dat[MAXN*4], lc[MAXN*4], rc[MAXN*4], l[MAXN*4], r[MAXN*4], root, top;
int tag[MAXN*4];
seg_tree()
{ root = top = 0; }
void build(int &nd, int opl, int opr)
{
nd = ++top, l[nd] = opl, r[nd] = opr;
if (opl < opr) {
int mid = (opl+opr)>>1;
build(lc[nd], opl, mid), build(rc[nd], mid+1, opr);
}
}
void pdw(int nd)
{
if (lc[nd]) tag[lc[nd]] += tag[nd], tag[rc[nd]] += tag[nd];
dat[nd] += tag[nd];
tag[nd] = 0;
}
void modify(int nd, int opl, int opr, int dt)
{
pdw(nd);
if (l[nd] == opl && r[nd] == opr) tag[nd] += dt;
else {
int mid = (l[nd]+r[nd])>>1;
if (opr <= mid) modify(lc[nd], opl, opr, dt);
else if (opl > mid) modify(rc[nd], opl, opr, dt);
else modify(lc[nd], opl, mid, dt), modify(rc[nd], mid+1, opr, dt);
pdw(lc[nd]), pdw(rc[nd]), dat[nd] = max(dat[lc[nd]], dat[rc[nd]]);
}
}
int query(int nd, int opl, int opr)
{
pdw(nd);
if (l[nd] == opl && r[nd] == opr) return dat[nd];
else {
int mid = (l[nd]+r[nd])>>1;
if (opr <= mid) return query(lc[nd], opl, opr);
else if (opl > mid) return query(rc[nd], opl, opr);
else return max(query(lc[nd], opl, mid), query(rc[nd], mid+1, opr));
}
}
} seg;
struct node {
int to, next;
} edge[MAXN*2];
int head[MAXN], ttop = 0;
inline void push(int i, int j)
{ edge[++ttop] = (node) {j, head[i]}, head[i] = ttop; }
int father[MAXN][18];
int chl[MAXN][2], fa[MAXN], top = 0;
inline bool is_rt(int nd)
{ return chl[fa[nd]][0] != nd && chl[fa[nd]][1] != nd; }
void zig(int nd)
{
int p = fa[nd], g = fa[p], tp = chl[p][0] != nd, tg = chl[g][0] != p;
int son = chl[nd][tp^1];
if (son) fa[son] = p;
if (!is_rt(p)) chl[g][tg] = nd;
fa[p] = nd, fa[nd] = g, chl[nd][tp^1] = p, chl[p][tp] = son;
}
void splay(int nd)
{
while (!is_rt(nd)) {
int p = fa[nd], g = fa[p], tp = chl[p][0] != nd, tg = chl[g][0] != p;
if (is_rt(p)) { zig(nd); break; }
else if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}
}
int dfn[MAXN], out[MAXN], dfn_top = 0, depth[MAXN];
void dfs(int nd, int f, int bef = 0)
{
fa[nd] = father[nd][0] = f, depth[nd] = bef;
for (int j = 1; j <= 17; j++)
father[nd][j] = father[father[nd][j-1]][j-1];
dfn[nd] = ++dfn_top;
seg.modify(seg.root, dfn[nd], dfn[nd], bef);
// cerr << nd << " " << dfn[nd] << " " << bef << endl;
for (int i = head[nd]; i; i = edge[i].next) {
int to = edge[i].to;
if (to == f) continue;
dfs(to, nd, bef+1);
}
out[nd] = dfn_top;
}
int lca(int a, int b)
{
if (depth[a] < depth[b]) swap(a, b);
int d = depth[a]-depth[b];
for (int i = 0; i < 18; i++)
if (d&(1<<i))
a = father[a][i];
if (a == b) return a;
for (int i = 17; i >= 0; i--)
if (father[a][i] != father[b][i])
a = father[a][i], b = father[b][i];
return father[a][0];
}
void update(int y, int dt)
{
if (!y) return;
seg.modify(seg.root, dfn[y], out[y], dt);
}
void access(int x)
{
for (int y = 0; x; x = fa[y = x]) {
splay(x);
int nd = chl[x][1];
while (chl[nd][0]) nd = chl[nd][0];
update(nd, 1);
chl[x][1] = y;
nd = y;
while (chl[nd][0]) nd = chl[nd][0];
update(nd, -1);
}
}
int n, m, a, b, opt;
int main()
{
scanf("%d%d", &n, &m);
seg.build(seg.root, 1, n);
for (int i = 1; i < n; i++) {
scanf("%d%d", &a, &b);
push(a, b), push(b, a);
}
dfs(1, 0);
for (int i = 1; i <= m; i++) {
scanf("%d", &opt);
if (opt == 1) {
scanf("%d", &a);
access(a);
} else if (opt == 2) {
scanf("%d%d", &a, &b);
int c = lca(a, b);
int dat = seg.query(seg.root, dfn[a], dfn[a])+seg.query(seg.root, dfn[b], dfn[b]);
dat -= seg.query(seg.root, dfn[c], dfn[c])*2-1;
printf("%d\n", dat);
} else if (opt == 3) {
scanf("%d", &a);
printf("%d\n", seg.query(seg.root, dfn[a], out[a])+1);
} else throw;
}
return 0;
}
loj#2002. 「SDOI2017」序列计数
首先要容斥…就是用所有数的方案减去只有非素数的方案。
然后就是经典题了,考虑dp,考虑倍增这个dp,考虑FFT优化。
其实不写FFT已经稳中稳了..p可以开到 50000 的..
#include <bits/stdc++.h>
using namespace std;
const int MAXP = 100, mod = 20170408, MAXM = 2e7+1;
int n, m, p;
struct Dp {
int dp[MAXP];
friend Dp operator * (const Dp &a, const Dp &b)
{
Dp ret;
memset(ret.dp, 0, sizeof ret.dp);
for (int i = 0; i < p; i++)
for (register int j = 0; j < p; j++)
ret.dp[(i+j)%p] = (ret.dp[(i+j)%p]+(long long)a.dp[i]*b.dp[j]%mod)%mod;
return ret;
}
friend Dp operator ^ (Dp a, int n)
{
Dp ans;
memset(ans.dp, 0, sizeof ans.dp);
ans.dp[0] = 1;
for (register int i = 0; i <= 30; i++) {
if (n&(1<<i)) ans = ans*a;
a = a*a;
}
return ans;
}
} A, NPO;
int prime[MAXM], not_prime[MAXM], top = 0;
void get_prime(int N)
{
A.dp[1] = NPO.dp[1] = 1;
for (int i = 2; i <= N; i++) {
A.dp[i%p]++;
if (!not_prime[i]) prime[++top] = i;
else NPO.dp[i%p]++;
for (register int j = 1; j <= top && prime[j]*i <= N; j++) {
not_prime[prime[j]*i] = 1;
if (i%prime[j] == 0) break;
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &p);
get_prime(m);
A = A^n, NPO = NPO^n;
printf("%d\n", ((A.dp[0]-NPO.dp[0])%mod+mod)%mod);
return 0;
}
loj#2003. 「SDOI2017」新生舞会
二分答案,费用流验证…
多路增广会退化而TLE一个点…还得用单路增广。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 205;
struct node {
int to, next, flow;
double cost;
int neg;
} edge[MAXN*MAXN*4];
int head[MAXN], top = 0;
inline void push(int i, int j, int flow, double dis)
{
++top, edge[top] = (node) {j, head[i], flow, dis, top+1}, head[i] = top;
++top, edge[top] = (node) {i, head[j], 0, -dis, top-1}, head[j] = top;
}
double dis[MAXN];
bool vis[MAXN];
queue<int> que;
const int S = MAXN-2, T = S+1;
const double eps = 1e-8;
int pre[MAXN], pre_edge[MAXN];
bool spfa(int &flow, double &cost)
{
memset(vis, 0, sizeof vis);
for (register int i = 1; i < MAXN; i++) dis[i] = 1e20;
dis[S] = 0, que.push(S), vis[S] = 1;
while (!que.empty()) {
int nd = que.front(); que.pop(), vis[nd] = 0;
// cerr << nd << " " << dis[nd] << endl;
for (int i = head[nd]; i; i = edge[i].next) {
int to = edge[i].to;
if (!edge[i].flow || dis[to] <= dis[nd]+edge[i].cost+eps) continue;
dis[to] = dis[nd]+edge[i].cost;
pre[to] = nd, pre_edge[to] = i;
if (!vis[to]) vis[to] = 1, que.push(to);
}
}
if (dis[T] > 1e19) return 0;
int maxf = INT_MAX;
for (int i = T; i != S; i = pre[i]) maxf = min(maxf, edge[pre_edge[i]].flow);
for (int i = T; i != S; i = pre[i])
edge[pre_edge[i]].flow -= maxf, edge[edge[pre_edge[i]].neg].flow += maxf;
flow += maxf, cost += maxf*dis[T];
return 1;
}
void mcf(int &flow, double &cost)
{
flow = cost = 0;
int cnt = 0, t;
while (spfa(flow, cost));
}
int n;
int a[101][101], b[101][101];
bool judge(double k)
{
top = 0, memset(head, 0, sizeof head);
for (int i = 1; i <= n; i++) push(S, i, 1, 0);
for (int i = 1; i <= n; i++) push(i+n, T, 1, 0);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
push(i, j+n, 1, -a[i][j]+b[i][j]*k);
int flow;
double cost;
mcf(flow, cost);
// cerr << flow << " " << cost << endl;
return flow == n && cost < 0;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%d", &b[i][j]);
double l = 0, r = 1e5, mid;
while (r-l >= eps) {
mid = (l+r)/2;
if (judge(mid)) l = mid;
else r = mid;
}
printf("%.6f\n", (l+r)/2);
return 0;
}
loj#2004. 「SDOI2017」硬币游戏
比较神…
思想就是用N表示所有没有人获胜的状态,然后列方程
对于A,B两个串例如:
A=TTH, B=HTT
那么N+TTH一定会到终止点,但不一定TTH加完后才停止
NTTH = A + BH + BTH
0.125N = A + 0.75B
获胜概率和为1
n+1个变量 n+1个方程 高斯消元怎么解释呢?
就是把一些状态的概率用一些变量表示出来了呀
计算系数可以两两用kmp,求B再方程A中的系数:AB连起来求fail,然后得到后缀=前缀了
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 305;
double g[MAXN][MAXN];
char str[MAXN][MAXN];
char s[MAXN*2];
int nxt[MAXN*2];
int n, m;
double border(int u, int v)
{
for (register int i = 1; i <= m; i++) s[i] = str[u][i], s[m+i] = str[v][i];
nxt[0] = nxt[1] = 0;
int p = 0;
for (register int i = 2; i <= m*2; i++) {
while (p && s[i] != s[p+1]) p = nxt[p];
if (s[i] == s[p+1]) p++;
nxt[i] = p;
}
double ans = 0;
int pt = nxt[m<<1];
while (pt > m) pt = nxt[pt];
while (pt) {
//cerr << pt << " ";
ans += pow(0.5, m-pt), pt = nxt[pt];
}
// cerr << endl;
return ans;
}
const double eps = 1e-200;
void solve()
{
/* for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n+1; j++)
cerr << g[i][j] << " ";
cerr << endl;
}*/
for (int i = 1; i <= n; i++) {
int j = i;
while (abs(g[j][i]) < eps) j++;
swap(g[i], g[j]);
for (int j = 1; j <= n; j++) {
if (i == j) continue;
double tmp = -g[j][i]/g[i][i];
for (int k = 1; k <= n+1; k++)
g[j][k] += tmp*g[i][k];
}
}
for (int i = 1; i < n; i++)
printf("%.10f\n", g[i][n+1]/g[i][i]);
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%s", str[i]+1);
// cerr << border(3, 1) << endl;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
g[i][j] = border(i, j);
}
g[i][n+1] = -pow(0.5, m);
}
for (int i = 1; i <= n; i++) g[n+1][i] = 1;
g[n+1][n+2] = 1;
n++;
solve();
return 0;
}
loj#2005. 「SDOI2017」相关分析
这个题和THUSC那个题是一个模型…
用正规公式 AT(Ac⃗ −y⃗ )=0 可以推出最小二乘法的另一种形式:
然后设向量:
会发现后两种操作都是矩阵..所以就证明了操作具有结合律、操作对向量求和具有分配率,可以拿线段树维护…
然而直接无脑矩阵显然TLE…因此要用分类讨论..
另:爆long long…用__int128即可
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
struct dat {
int l, r;
__int128 sumx, sumxx, sumy, sumxy;
friend dat operator + (const dat &a, const dat &b)
{ return (dat) {a.l, b.r, a.sumx+b.sumx, a.sumxx+b.sumxx, a.sumy+b.sumy, a.sumxy+b.sumxy}; }
};
struct ltag {
__int128 S, T;
int tp;
friend ltag operator * (const ltag &a, const ltag &b)
{
if (a.tp == 1 && b.tp == 1) return (ltag) {a.S+b.S, a.T+b.T, 1};
if (b.tp == 2) return b;
return (ltag) {a.S+b.S, a.T+b.T, 2};
}
friend dat operator * (const dat &a, const ltag &t)
{
int L = a.r-a.l+1;
if (t.tp == 1)
return (dat) {a.l, a.r, a.sumx+L*t.S, a.sumxx+t.S*t.S*L+2*t.S*a.sumx,
a.sumy+t.T*L, a.sumxy+a.sumx*t.T+a.sumy*t.S+t.S*t.T*L};
else {
__int128 sumi = (__int128)(a.l+a.r)*L/2, sumii = (__int128)a.r*(a.r+1)*(2*a.r+1)/6-(__int128)a.l*(a.l-1)*(2*a.l-1)/6;
return (dat) {a.l, a.r, sumi+t.S*L, sumii+t.S*t.S*L+2*t.S*sumi,
sumi+t.T*L, sumii+t.S*t.T*L+(t.S+t.T)*sumi};
}
}
};
inline ltag I()
{ return (ltag) {0, 0, 1}; }
dat tree[MAXN*4];
ltag tag[MAXN*4];
int lc[MAXN*4], rc[MAXN*4], top = 0, root = 0;
int x[MAXN], y[MAXN];
int n, m;
void build(int &nd, int l, int r)
{
nd = ++top;
if (l == r) tree[nd] = (dat) {l, l, x[l], (__int128)x[l]*x[l], y[l], (__int128)y[l]*x[l]}, tag[nd] = I();
else {
build(lc[nd], l, (l+r)/2), build(rc[nd], (l+r)/2+1, r);
tag[nd] = I(), tree[nd] = tree[lc[nd]]+tree[rc[nd]];
}
}
void pdw(int nd)
{
if (lc[nd]) tag[lc[nd]] = tag[lc[nd]]*tag[nd], tag[rc[nd]] = tag[rc[nd]]*tag[nd];
tree[nd] = tree[nd]*tag[nd], tag[nd] = I();
}
void modify(int nd, int l, int r, const ltag &tgg)
{
pdw(nd);
if (tree[nd].l == l && tree[nd].r == r) tag[nd] = tag[nd]*tgg;
else {
int mid = (tree[nd].l+tree[nd].r)>>1;
if (r <= mid) modify(lc[nd], l, r, tgg);
else if (l > mid) modify(rc[nd], l, r, tgg);
else modify(lc[nd], l, mid, tgg), modify(rc[nd], mid+1, r, tgg);
pdw(lc[nd]), pdw(rc[nd]), tree[nd] = tree[lc[nd]]+tree[rc[nd]];
}
}
dat query(int nd, int l, int r)
{
pdw(nd);
if (tree[nd].l == l && tree[nd].r == r) return tree[nd];
else {
int mid = (tree[nd].l+tree[nd].r)>>1;
if (r <= mid) return query(lc[nd], l, r);
else if (l > mid) return query(rc[nd], l, r);
else return query(lc[nd], l, mid)+query(rc[nd], mid+1, r);
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &x[i]);
for (int i = 1; i <= n; i++) scanf("%d", &y[i]);
build(root, 1, n);
for (int i = 1; i <= m; i++) {
int tp, L, R, S, T;
scanf("%d", &tp);
if (tp == 1) {
scanf("%d%d", &L, &R);
dat qy = query(root, L, R);
int len = R-L+1;
printf("%.10f\n", ((double)len*qy.sumxy-(double)qy.sumx*qy.sumy)/(-(double)qy.sumx*qy.sumx+(double)len*qy.sumxx));
} else if (tp == 2) {
scanf("%d%d%d%d", &L, &R, &S, &T);
modify(root, L, R, (ltag) {S, T, 1});
} else {
scanf("%d%d%d%d", &L, &R, &S, &T);
modify(root, L, R, (ltag) {S, T, 2});
}
}
return 0;
}