LOJ刷题记录:2000-2005(SDOI2017)

LOJ刷题记录:2000-2005(SDOI2017)

立一个flag…loj不跳题刷题233


loj2000. 「SDOI2017」数字表格

求:

1in1jmf(gcd(i,j))

考虑枚举 gcd(i,j) 则原式为:

df(d)g(d)g(d)=1in1jm[(i,j)=d]=pμ(p)ndpmdp

因此可以下底函数分块求。注意如果要对指数取模应该取 φ(Mod)=Mod1

#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 可以推出最小二乘法的另一种形式:

a=nxiyixiyinx2i(xi)2

然后设向量:

xx2xyy1

会发现后两种操作都是矩阵..所以就证明了操作具有结合律、操作对向量求和具有分配率,可以拿线段树维护…

然而直接无脑矩阵显然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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值