12 月日记

关于11月的日记:

12.1

是 Sunday,我们有救了,打了一天 codm 已红温

今天一整天刷到的都是日漫刀子,每个的 bgm 都是 cry for me,已经被刀怕了,听完更压抑了

晚上返校

12.2

一整天都在各种基础 dp

P1171 售货员的难题

tsp 板子

P1776 宝物筛选

二进制/优先队列优化多重背包

有点颓,明天不能再颓了

\(\Large{\text{Cry For Me.}}\)

12.3

昨天晚上打了 Edu,困

最近这几天总感觉自己很郁闷,大抵是因为最近被刀的太狠了,做题啥的都没什么兴致,一整个打不起精神。昨晚十二点半 Edu 结束一直睡不着,心里很压抑,总感觉学习生活好乏味,整个人都不太好,只有踢球和听歌的时候可能可以放松一下,一整个不舒服 (果然也挺啰嗦

今天找了基础 dp 题单来做,想着先把基础打好再说(zml 也是这么给我说的,我跟不上我们机房的进度了)

P1896 [SCOI2005] 互不侵犯

经典状压

\(dp_{i,j,k}\) 表示前 \(i\) 行有 \(k\) 个国王,当前状态为 \(j\) 的方案数,对于每个状态可以拆分成二进制预处理,在转移的时候判断当前点是否可以放置国王,有转移方程:\(dp_{i,j,k} \leftarrow dp_{i - 1,p,t} + dp_{i,j,k}\),其中 \(p\) 为所枚举的状态,\(t\) 为所枚举的国王数,答案即为:\(\displaystyle\sum_{i=1}^{n}\sum^{state}_{j=1}{dp_{i,j,k}}\)

code

#include<bits/stdc++.h>
#define int long long

using namespace std;

#define read(x) scanf("%lld",&x)
#define write(x) printf("%lld\n",x)

const int MAXN = 1e7 + 10;
int n,k,dp[15][15333][83],ind,Answer;
int king[MAXN],state[MAXN];

inline void init()
{
    int m = (1 << n) - 1;
    for(int i = 0;i <= m;i ++)
    {
        if(!((i << 1) & i))
        {
            state[++ ind] = i;
            int tmp = i;
            while(tmp)
            {
                king[ind] += tmp % 2;
                tmp >>= 1;
            }
        }
    }
}

signed main()
{
    read(n),read(k);
    init();
    for(int i = 1;i <= ind;i ++)
    {
        if(king[i] <= k)
            dp[1][i][king[i]] = 1;
    }
    for(int i = 2;i <= n;i ++)
    {
        for(int j = 1;j <= ind;j ++)
        {
            for(int p = 1;p <= ind;p ++)
            {
                if(state[j] & state[p]) continue;// 上下
                if(state[j] & (state[p] << 1)) continue;// 右上
                if((state[j] << 1) & state[p]) continue;// 左下
                for(int t = 1;t <= k;t ++)
                {
                    if(king[j] + t > k) continue;
                    dp[i][j][king[j] + t] += dp[i - 1][p][t];
                }
            }
        }
    }
    for(int i = 1;i <= n;i ++)
    {
        for(int j = 1;j <= ind;j ++)
            Answer += dp[i][j][k];
    }
    write(Answer);
    return 0;
}

その日まで
直至此日

飛ばあげ
展翅腾飞

嵐がさわってゆくゆくまで
直至触碰狂风暴雨

12.4

上午 %你赛

没写 T1 \(0 + 40 + 10 + 40 = 90 \texttt{ rank 5 }\)

「总是羡慕别人的优秀,自己却只能无能为力,本就弱小的心灵,却淹没在了汹涌潮水中」

T1

赛时一点不想写字符串

容易发现对于两个前后匹配的 \(\texttt{B}\),经过排列后答案会 \(\times n!\),所以我们只需要去找这种匹配的 \(\texttt{B}\),注意只能是匹配的,如若不然则无解。

code

#include<bits/stdc++.h>
#define int long long

using namespace std;
const int MAXN = 1e5 + 10;
const int mod = 1e9 + 7;
char str[MAXN << 1];
int fac[MAXN],n,T;

inline void init()
{
    fac[0] = 1;
    for(int i = 1;i <= 1e5;i ++)
        fac[i] = fac[i - 1] * i % mod;
}

inline void Solve()
{
    scanf("%lld%s",&n,str + 1);
    int Answer = (str[1] == 'B' && str[n * 2] == 'B'),tmp = 1;
    for(int i = 2;i <= n * 2 - 1;i ++)
    {
        if((str[i] == 'B') == (tmp & 1)) Answer = Answer * tmp % mod,tmp --;
        else tmp ++;
    }
    Answer = Answer * (tmp == 1) * fac[n] % mod;
    printf("%lld\n",Answer);
}

signed main()
{
    scanf("%lld",&T);
    init();
    while(T --)
        Solve();
    return 0;
}

T2

看题第一眼,不是哥们我怎么输入(?

先考虑 \(\texttt{-1}\) 的情况,可以并查集判 \(u,v\) 是否处于统一连通块,因为是无向图,若不在同意连通块则没有路径。

求路径长度可以用倍增 \(\text{LCA}\),复杂度 \(O(q \log n)\)

code

#include<bits/stdc++.h>
#define int long long

using namespace std;

char ch = getchar();
inline int read()
{
	int T = 0;
    bool flag = 1; 
	while(!isdigit(ch)){if(ch == '-') flag = 0;ch = getchar();}
	while(isdigit(ch)){T = (T << 1) + (T << 3) + (ch ^ 48);ch = getchar();}
	if(flag) return T;
	return ~(T - 1);
}

const int MAXN = 5e5 + 10;
int n,q,idx,head[MAXN],p[MAXN][30],dsu[MAXN],depth[MAXN],dis[MAXN];
struct node{int v,w,nxt;}edge[MAXN << 1];

inline void add(int u,int v,int w)
{
    edge[++ idx].v = v;
    edge[idx].w = w;
    edge[idx].nxt = head[u];
    head[u] = idx;
}

int find(int x)
{
    if(dsu[x] == x)
        return x;
    return dsu[x] = find(dsu[x]);
}

inline void merge(int u,int v)
{
    int fu = find(u),fv = find(v);
    // if(fu != fv)
        dsu[fu] = fv;
}

void dfs(int u,int fa)
{
    depth[u] = depth[fa] + 1;
    p[u][0] = fa;
    for(int i = 1;i <= 20;i ++)
        p[u][i] = p[p[u][i - 1]][i - 1];
    for(int i = head[u];i;i = edge[i].nxt)
    {
        int v = edge[i].v,w = edge[i].w;
        if(v == fa) continue;
        dis[v] = dis[u] + w;
        dfs(v,u);
    }
}

int Lca(int u,int v)
{
    if(depth[u] > depth[v]) swap(u,v);
    for(int i = 20;i >= 0;i --)
    {
        if(depth[v] - (1 << i) >= depth[u]) v = p[v][i];
    }
    if(u == v) return u;
    for(int i = 20;i >= 0;i --)
    {
        if(p[u][i] != p[v][i]) u = p[u][i],v = p[v][i];
    }
    return p[u][0];
}

signed main()
{
    n = read(),q = read();
    for(int i = 1;i <= n;i ++) dsu[i] = i;
    while(q --)
    {
        int u,v,w;
        u = read(),v = read();
        if(ch == ' ')
        {
            w = read();
            add(u,v,w);
            add(v,u,w);
            merge(u,v);
            dis[u] = dis[v] + w;
            dfs(u,v);
        }
        else
        {
            if(find(u) != find(v)) printf("-1\n");
            else printf("%lld\n",dis[u] + dis[v] - 2 * dis[Lca(u,v)]);
        }
    }
    return 0;
}

今天做状压

P1879 [USACO06NOV] Corn Fields G

对于每一行,它是相对独立的,记 \(dp_{i,j}\) 表示当前在第 \(i\) 行,状态为 \(j\) 的方案数,有转移:\(dp_{i,j} \leftarrow dp_{i,j} + dp_{i - 1,k}\),初始化 \(dp_{0,0} = 1\)

预处理每一行的状态 \(state_i\) 表示是否满足隔开的状态。

code

#include<bits/stdc++.h>
// #define int long long

using namespace std;
#define read(x) scanf("%d",&x)
#define write(x) printf("%d\n",x)

const int mod = 1e8;
const int MAXN = 5e4 + 1;
int m,n,field[15][15],vec[MAXN],dp[15][MAXN];
bool state[MAXN];

signed main()
{
    read(m),read(n);
    for(int i = 1;i <= m;i ++)
    {
        for(int j = 1;j <= n;j ++)
            read(field[i][j]);
    }
    int len = (1 << n);
    for(int i = 1;i <= m;i ++)
    {
        for(int j = 1;j <= n;j ++)
            vec[i] = (vec[i] << 1) + field[i][j];
    }
    dp[0][0] = 1;
    for(int i = 0;i < len;i ++)
        state[i] = (!(i & (i >> 1)) && !(i & (i << 1)));
    for(int i = 1;i <= m;i ++)
    {
        for(int j = 0;j < len;j ++)
        {
            if(state[j] && (vec[i] & j) == j)
            {
                for(int k = 0;k < len;k ++)
                {
                    if(!(j & k))
                        dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;
                }
            }
        }
    }
    int Answer = 0;
    for(int i = 0;i < len;i ++)
        Answer = (Answer + dp[m][i]) % mod;
    write(Answer);
    return 0;
}

\(「刮风这天,我试过握着你手,但偏偏,雨渐渐,大到我看你不见」\\ ——\texttt{Jay Chou}《晴天》\)

12.5

晚自习调代码红温了,没时间写了

12.6

关于昨天调红温的题:CF1900E Transitive Graph

死因:多测没清空

solution

由于原图经过不断的操作,最后所有的 \(a\) 一定能到达 \(c\),所以它们就在一个强连通分量里,缩点之后就是一个 \(\texttt{DAG}\),就可以拓扑 + \(\texttt{dp}\) 求出答案

Morning

上午 %你赛,机房各位参加了 \(\texttt{NOIP}\) 的大佬今天下午就出分了,除我以外 (Orzzzzz

由于是机房大佬 \(\texttt{JMR}\) 出的题,难度 \(\texttt{S}\) 组 (?

实际 \(\texttt{NOIP or NOIP+}\) ?(我不道啊),终榜 \(100 + 0 + 0 + 0 = 100 \texttt{pts}\)

T1

赛时开题 \(2h\) 后我仍然盯着写满了的两页草稿纸发呆想结论,思考良久认为之前有做过原题(实际上就是原题:P6280 [USACO20OPEN] Exercise G),大概明白这一点之后,就往环的方向想,然后发现对于一个排列 \(p \in P_n\),要使得 \(p_i = i\) 的操作次数为 \(\text{lcm }{p_i}\),对于如何求一个序列的 \(\text{lcm}\),我们可以对 \([1,n]\) 的每个数分解质因数,取最高次的质因子,去枚最高能取多少次然后相乘。

首先对于原题中答案为 \(\displaystyle\sum_{x \in F_n}{x}\),我们考虑先转化问题:统计 \(x \in F_n\) 的个数,显而易见这是一道计数题(有原题:P4161 [SCOI2009] 游戏),可以用 \(\texttt{dp}\) 求解,所以我们考虑这道题也用 \(\texttt{dp}\)

\(dp_{i,j}\) 表示前 \(i\) 个质因子的和为 \(j\) 时幂次 \(\alpha\) 的和,由于这里是 \(i\) 还没被使用过的情况,所以转移要乘上 \(p_i^{\alpha}\),则有转移:\(dp_{i,j} \leftarrow dp_{i,j} + dp_{i-1,j-p_i^{\alpha}} \times p_i^{\alpha}\),容易发现这是一个背包问题,所以可以滚掉第一维,答案即为前 \(i\) 个质因子和为 \(j \in [0,n]\) 的情况之和,即为 \(\displaystyle\sum_{i=0}^{n}{dp_i}\)

T2

很明显我们要处理的就是 \(\lvert{x_i - x_j}\rvert + \lvert{y_i - y_j}\rvert\)\(r_i + r_j\) 的关系,看起来貌似要处理有很多项,大抵是比较繁琐的,移项可得 \(\displaystyle\min_j\{\lvert{x_i - x_j}\rvert + \lvert{y_i - y_j}\rvert - r_i\}\)\(r_j\) 的关系,对于 \(\lvert{x_i - x_j}\rvert + \lvert{y_i - y_j}\rvert\),我们可以看作 \((x_i,y_i)\)\((x_j,y_j)\) 连了一条长度为 \(\texttt{1}\) 的无向边然后跑最短路的结果,对于 \(\displaystyle\min_j\{\lvert{x_i - x_j}\rvert + \lvert{y_i - y_j}\rvert - r_i\}\),我们可以从超级源点连一条 \(-r_i\) 的有向边,对于每个点都只能从每个花环转移,边界条件即为花环的一周为 \(\texttt{-1}\)

但是,常规的 \(\texttt{dijkstra}\) 复杂度为 \(O(n \times m^2 \log m)\),但是在边权仅为 \(\texttt{1}\) 的情况下,\(\texttt{bfs}\) 以其优秀的复杂度 \(O(m \times n)\) 映入我们的眼帘,因此目前只有 \(q\) 次询问的复杂度需要优化了。

不得不说 \(\texttt{JMR}\) 大佬的思路确实很好,对询问分块,对于每个块暴力处理,最后 \(\texttt{bfs}\) 求最短路,记 \(lenV\) 为块长,复杂度 \(O(\frac{q}{lenV} \times n \times m + q \times lenV)\),可以接受。

code

#include<bits/stdc++.h>
// #define int long long

using namespace std;
const int MAXN = 2e5 + 10;
const int dx[] = {0,1,0,-1};
const int dy[] = {1,0,-1,0};
const int inf = 1e9;
int n,m,q,x[MAXN],y[MAXN],r[MAXN];
int mp[2000005],Answer;
bool flag[MAXN];
vector <int> vec;

inline void read(int &T)
{
    T = 0;
    int f = 1;
    char ch = getchar();
    while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}
    while(isdigit(ch)){T = (T << 1) + (T << 3) + (ch ^ 48);ch = getchar();}
    T *= f;
}

void modify(int x,int y,int k)
{
    int val = ((k >> 1) ? -inf : -1);
    if(x < 0 || x >= n || y < 0 || y >= m) return;
    if(val == -1)
        vec.push_back(x * m + y);
    mp[x * m + y] = val;
}

void Solve_flag(int x,int y,int r)
{
    for(int i = 0;i < r;i ++)
    {
        for(int j = 0;j + i < r;j ++)
        {
            modify(x + i,y + j,r - i - j);
            if(i) modify(x - i,y + j,r - i - j);
            if(j) modify(x + i,y - j,r - i - j);
            if(i && j) modify(x - i,y - j,r - i - j);
        }// min{|xi - xj| + |yi - yj| - ri} 与 rj
    }
}

void bfs()
{
    queue <int> q;
    for(auto x : vec) q.push(x);
    while(!q.empty())
    {
        while(!q.empty())
        {
            int head = q.front(),x = head / m,y = head % m;
            q.pop();
            for(int i = 0;i < 4;i ++)
            {
                int nx = x + dx[i],ny = y + dy[i];
                if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                if(mp[nx * m + ny] > mp[head] + 1)
                {
                    mp[nx * m + ny] = mp[head] + 1;
                    q.push(nx * m + ny);
                }
            }
        }
    }
}

signed main()
{
    read(n),read(m),read(q);
    int lenV = 1000;
    for(int i = 0;i < n * m;i ++) mp[i] = inf;// mp 将整个矩阵压成一维
    for(int i = 1;i <= q;i ++)
    {
        read(x[i]),x[i] --;
        read(y[i]),y[i] --;
        read(r[i]);
        if(r[i] >= n + m)
            r[i] = n + m - 1;
        if(mp[x[i] * m + y[i]] - r[i] + 1 >= 0)
        {
            flag[i] = 1;
            for(int j = i - 1;j % lenV;j --)
            {
                if(flag[j])
                {
                    if(abs(x[i] - x[j]) + abs(y[i] - y[j]) - r[i] - r[j] + 1 < 0)
                    {
                        flag[i] = 0;
                        break;
                    }
                }
            }// 对于每个块暴力处理
            if(flag[i])
            {
                Answer ++;
                Solve_flag(x[i],y[i],r[i]);
            }
        }
        if(i % lenV == 0) bfs();
    }
    printf("%d\n",Answer);
    for(int i = 1;i <= q;i ++)
    {
        if(flag[i])
            printf("%d ",i);
    }
    return 0;
}

今天还是状压专题

P1441 砝码称重

对于一个砝码序列,\(\texttt{1}\) 表示该砝码存在,\(\texttt{0}\) 表示该砝码不存在,所以有 \(2^n\) 种情况,考虑状压,枚举每种情况,用 \(\texttt{bitset}\) 存每种情况最多可放几种,答案即为 \(\max\{bitset中1的个数\} - 1\)

\(1 << n\) 打成 \(n << 1\) 了,警钟长鸣

P1433 吃奶酪

经典 \(\texttt{tsp}\),开 \(\texttt{long double}\) 精度太高炸了 (?

警钟长鸣

人们总是不敢面对自己的弱小,却不知他们不敢面对的是未来的基石 ——沃兹基·硕德

12.7

被拉去讲了两道题:CF2030E CF2027D2

只能说讲题能力仍需提升,讲得跟依托一样,同志仍需加强对题目解法的理解,还得理清楚思路(

CF2025E Card Game

标题一看,好 kards(但是我不玩

\(dp_{i,j}\) 表示当前为第 \(i\) 级的卡牌,我们选了 \(j\) 张卡牌的方案数,初始化 \(dp_{i,0} = 1\),由于是对半分,所以有约束 \(2j \leq i\),可以得到转移:\(dp_{i,j} \leftarrow dp_{i-1,j-1} + dp_{i-1,j}[2j \leq i]\),对于特殊的 \(\texttt{1}\) 花色卡牌,记 \(g_{i,j}\) 表示考虑完 \(1\)\(i\) 的卡牌后,剩余的 \(\texttt{1}\) 花色卡牌的数量为 \(j\),可以得到转移:\(g_{i,j} = g_{i-1,j+k} \times dp_{m,\frac{(m-k)}{2}}\),其中需要保证 $2 \mid (m-k) $,最终答案即为 \(g_{n,0}\)

12.8

今天正儿八经的日记,不是题解

昨天周六罕见的没有熬夜玩原神,大抵是太困了所以睡得很早,但是周天私人生物钟依然让我睡到九点多,好嘛大懒觉。本打算出去骑车的,毕竟正好买了个运动手套,还保暖,可是一看楼下地面湿滑直接给我劝退,然后就看手机 + 玩实况,最近突然对统计学很感兴趣,所以乱翻一通找了个笔记本开始翻书学习,当然里面概率那章肯定更重要点。

中午母亲大人炖了羊肉,豪赤。

下午继续学习 + 打 codm,打狙手感极好,自我感觉大肘子

晚上返校,场切了道蓝,开心 awa

12.9

由于昨晚打 cf,今天在学校也是睡到自然醒,托同学帮忙带了早饭。

今天开始做 \(\texttt{dp}\) 优化专题,也是赶紧去看了几篇博客然后开做。

P1613 跑路

显然是最短路,但是它又不能直接跑最短路,我们预处理一个 \(d_{i,j,type}\) 表示从 \(i,j\)\(2^{type}\) 可以到达,然后 \(O(n^3)\) 处理,枚举起、终、中转点,然后若对于 \((u,v)\) 满足条件,就连边跑最短路,由于 \(N \leq 50\),任意方法都可 (spfa真好用

P1081 [NOIP2012 提高组] 开车旅行

对于小 \(\texttt{A}\) 和小 \(\texttt{B}\) 的不同策略,我们通过两个 \(\text{set}\) 维护,其中一个存正数,维护小 \(\texttt{B}\),另一个存负数,维护小 \(\texttt{A}\),随后建立分层图,对于 \(1\)\(n\) 的节点表示小 \(\texttt{B}\)\(n + 1\)\(2n\) 的节点表示小 \(\texttt{A}\),连边后是一片森林,建一个超级源点就成了一棵树,这棵树的边可以看做是被染了两种颜色,第二个问题就是要求树上不同颜色边数的和,在 \(\text{dfs}\) 的同时也可以同步处理第一个问题,当然优化就是倍增向上跳,只能说实现真的挺麻烦。

code

#include<bits/extc++.h>
#define int long long
#define inf 1e18

using namespace std;
using namespace __gnu_pbds;
template <typename T>
inline void read(T &x)
{
    T res = 0,f = 1;
    char ch = getchar();
    while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}
    while(isdigit(ch)){res = (res << 1) + (res << 3) + (ch ^ 48);ch = getchar();}
    x = res * f;
}
const int MAXN = 3e5 + 10;
bool flag[MAXN];
int idx,head[MAXN],n,h[MAXN],stx,m,p[MAXN][25],d[MAXN],q;
int fraca[MAXN],fracb[MAXN],suma[MAXN],sumb[MAXN],ta,tb;
struct Edge{int v,w,nxt;}edge[MAXN << 1];
gp_hash_table <int,int> mp;
multiset <int> s1,s2;

inline void add(int u,int v,int w)
{
    edge[++ idx].v = v;
    edge[idx].w = w;
    edge[idx].nxt = head[u];
    head[u] = idx;
}

inline void dfs(int u,int fa)
{
    flag[u] = 1;
    p[u][0] = fa;
    for(int i = 1;i <= 20;i ++)
        p[u][i] = p[p[u][i - 1]][i - 1];
    for(int i = head[u];i;i = edge[i].nxt)
    {
        int v = edge[i].v,w = edge[i].w;
        if(v == fa) continue;
        d[v] = d[u] + w;
        suma[v] = suma[u],sumb[v] = sumb[u];
        if(v <= n) sumb[v] += w;
        else suma[v] += w;
        dfs(v,u); 
    } 
}

inline void init()
{
    read(n);
    fill(d,d + n + 1,-inf);
    for(int i = 1;i <= n;i ++)
    {
        read(h[i]);
        s1.insert(h[i]),s2.insert(-h[i]);
        mp[h[i]] = i;
    }
    s1.insert(inf),s2.insert(inf);
    for(int i = 1;i < n;i ++)
    {
        s1.erase(h[i]),s2.erase(-h[i]);
        int ptr1 = *s1.lower_bound(h[i]),ptr2 = *s2.lower_bound(-h[i]);
        if(ptr1 != inf || ptr2 != inf)
        {
            if(ptr1 != inf && ptr1 - h[i] < ptr2 + h[i])
            {
                add(mp[ptr1] + n,i,ptr1 - h[i]);
                flag[i] = 1;
            }
            else if(ptr2 != inf)
            {
                add(mp[-ptr2] + n,i,ptr2 + h[i]);
                flag[i] = 1;
            }
        }
        int tmp = inf,opt = 0;
        if(ptr1 != inf && ptr1 - h[i] < ptr2 + h[i])
        {
            s1.erase(ptr1);
            tmp = ptr1;
            opt = 1;
        }
        else if(ptr2 != inf)
        {
            s2.erase(ptr2);
            tmp = ptr2;
            opt = 2;
        }
        ptr1 = *s1.lower_bound(h[i]),ptr2 = *s2.lower_bound(-h[i]);
        if(ptr1 != inf || ptr2 != inf)
        {
            if(ptr1 != inf && ptr1 - h[i] < ptr2 + h[i])
            {
                add(mp[ptr1],i + n,ptr1 - h[i]);
                flag[i + n] = 1;
            }
            else if(ptr2 != inf)
            {
                add(mp[-ptr2],i + n,ptr2 + h[i]);
                flag[i + n] = 1;
            }
        }
        if(opt == 1)
            s1.insert(tmp);
        if(opt == 2)
            s2.insert(tmp);
    }
    for(int i = 1;i <= (n << 1);i ++)
    {
        if(!flag[i]) add(n << 1 | 1,i,0);
    }
    d[n << 1 | 1] = 0;
    dfs(n << 1 | 1,0);
}



inline void Dp(int u,int ds)
{
    int v = u;
    for(int i = 20;i >= 0;i --)
    {
        if(p[v][i] < 0 || p[v][i] > (n << 1 | 1)) continue;
        if(d[p[v][i]] >= d[u] - ds) v = p[v][i];
    }
    ta = suma[u] - suma[v];
    tb = sumb[u] - sumb[v];
}

inline void Solve()
{
    h[n << 1 | 1] = inf;
    double frac_mn = inf;
    int mn = (n << 1) + 2;
    h[mn] = -inf;
    read(stx);
    for(int i = 1;i <= n;i ++)
    {
        Dp(n + i,stx);
        fraca[i + n] = ta,fracb[i + n] = tb;
    }
    for(int i = n + 1;i <= (n << 1);i ++)
    {
        if(fracb[i])
        {
            if(1.0 * fraca[i] / fracb[i] < frac_mn || (1.0 * fraca[i] / fracb[i] == frac_mn && h[i - n] > h[mn - n]))
            {
                frac_mn = 1.0 * fraca[i] / fracb[i];
                mn = i;
            }
        }
        else if(frac_mn == inf)
        {
            if(h[i - n] > h[mn - n]) mn = i;
        }
    }
    printf("%lld\n",mn - n);
    read(q);
    while(q --)
    {
        int s,x;
        read(s),read(x);
        Dp(n + s,x);
        printf("%lld %lld\n",ta,tb);
    }
}

signed main()
{
    init();
    Solve();
    return 0;
}

CF1516D Cut

首先我们知道,对于区间 \([l,r]\),只有区间的 \(\text{gcd}\) 等于 \(1\),才能保证 \(\displaystyle\prod^r_{i=l}{a_i} = lcm\{a_i\}(i \in [l,r])\),那么 \(\text{gcd and lcm}\) 都与质因数有关,我们就可以分解 \(a_i\) 的质因数,双指针找到满足条件的区间 \([i,r_i]\),其中 \(r_i\) 为枚举 \(l_i\) 能扩展到的最大右端点,然后就是套路倍增 \(\texttt{dp}\),记 \(dp_{i,j}\) 表示从 \(i\) 往右跳 \(2^j\) 个区间能到达的最右端点。

12.10

上午 \(\texttt{NOIP}\) %你赛,做不来 /kk

皇帝的新部分分,\(\text{rank inf}\)

P4644 [USACO05DEC] Cleaning Shifts S

\(l_i,r_i\) 为给的区间左右端点,记 \(dp_i\) 表示前 \(i\) 个区间都选了的最小花费,可以得到:\(dp_i = \displaystyle\min_{r_j >l_i,r_j > r_i}{dp_j} + cost_i\),按 \(r_i\) 排序可得:\(dp_{i} = \displaystyle\min_{r_j > l_i}{dp_j} + cost_i\)

可以发现要去维护两个东西:\(\displaystyle\min_{r_j >l_i}\)\(r_j >l_i\)。第一项即为前缀 \(\min\),第二项转化一下得:\(N - r_j < N - l_i\),变成了维护单点减,树状数组或线段树都可。

P2851 [USACO06DEC] The Fewest Coins G

观察题面转化成背包问题,对于买家 \(\text{FarmerJohn}\),他的硬币是有限的 \(c_i\) 个,对于卖家,硬币是无限的,所以说,卖家的计算为完全背包,\(\text{FarmerJohn}\) 的为多重背包。

可得到转移方程(\(dp1\) 表示卖家,\(dp2\) 表示买家):

\(dp_i = dp1_{i - T} + dp2_{i}\),其中 \(i \in [T,V + T]\)

可以看出思路就是去枚举找零的数量 \(i\),所以付的钱为 \(i + T\),其中 \(V\) 表示对于买家的背包容量,\(V = \displaystyle\max{v_i^2} + T\)

证明:

找零过程中非最大面值硬币最多用的次数为 \(\max{v_i}\)

考虑反证:

\(> \max{v_i}\),则一定有找到几个 \(v_i\),使得它们的和为 \(\max{v_i}\) 的倍数,也就意味着我们可以用 \(k\)\(\max{v_i}\) 去替代它们找零。(基于抽屉原理得到)

分讨:

  • 找零过程中有 \(\max{v_i}\) 存在

    整个过程中交易的硬币都不是 \(\max{v_i}\),所以付的钱以及找零的钱的数量 \(< \max{v_i^2}\)

  • 反之

    明显的,付的钱以及找零的钱的数量 \(< \max{v_i^2}\)

综上所述,在枚举的找零数量 \(i \geq T + \max{v_i^2}\),定存在方法减少交易数量,反之则无,最终可得对于买家的背包大小为 \(\max{v_i^2} + T\),最终答案为 \(\min{dp_i}\)

此题完结撒花!

每日歌曲

你停止,收讯号,我开始接受不到 ~

到底是谁知道,是几点钟方向,你才能收到暗号 ~

12.11

机房又风靡了一个新的小游戏,大抵是弱化版的维多利亚 (?

我要玩原神

UVA12983 The Battle of Chibi

很容易想到一个 \(\text{dp}\),记 \(dp_{i,j}\) 表示以 \(i\) 结尾长度为 \(j\) 的严格上升子序列的方案数,得到转移:\(dp_{i,j} = \sum{dp_{k,j-1}[a_k < a_i]}\),但是它的枚举是 \(O(n^2)\),转移是 \(O(n)\),无法接受。

我们可以用树状数组维护区间和,对于 \([a_k < a_i]\),考虑离散化,每次二分去找第一个大于等于 \(a_i\) 的数的位置,离散化的结果存的是原数组的下标,最终答案 \(\sum{dp_{i,m}}\),其中 \(i \in [1,n]\),意为枚举结尾的 \(i\),长度为 \(m\) 的答案。

P10978 Fence

\(dp_{i,j}\) 表示第 \(i\) 个工人涂了 \(j\) 块板子,有转移:

\( dp_{i,j} = \begin{cases}dp_{i-1,k} + (j - l + 1) \times p[k > j - l + 1] \\ dp_{i-1,k} + (j - k) \times p[k \leq j - l + 1]\end{cases} \)

其中 \(j \in [s,s+l-1],k \in [1,s-1]\),显然第一维可以拿掉,这样复杂度是 \(O(n^2k)\),无法接受。

对于第一种转移,我们可以树状数组维护,对于第二种,转化成 \(dp_j = \displaystyle\max_{k=j-l+1}^{j}\{dp_k + (j - k) \times p\} \rightarrow \{dp_k - kp + jp\}\),可以发现 \(jp\) 是定值,只需要考虑维护 \(dp_k - kp\),可以单调队列维护了,也就是复杂度 \(O(nk)\),可以接受,最终答案 \(\displaystyle\max_{i=0}^n{dp_i}\)

P10977 Cut the Sequence

双倍经验:P1848 P1295

\(dp_i\) 表示前 \(i\) 个已经分好段,有状态转移:\(dp_i = dp_{j - 1} + \displaystyle\max_{k=j}^{i}\{a_k\}\),满足 \(\displaystyle\sum^i_{k=j}{a_k} \leq m\) 前提下,复杂度 \(O(n^2)\),无法接受。

考虑优化,从单调性先入手,对于一个不变的 \(i\),当 \(j\) 单增时,\(\displaystyle\max_{k=j}^{i}\{a_k\}\) 单调不增,\(dp_{j-1}\) 单调不减,在我们枚举到 \(i\) 时,\(dp_{j-1}\) 并不会对转移产生影响。

考虑 \(a_i\) 对区间的影响,每次找到 \(a_{pos} > a_i\),其中 \(pos \in [1,i)\),显然可以发现对于 \(\displaystyle\max_{k=j}^{i}\{a_k\}(j > pos) \rightarrow a_i\),考虑线段树维护这个操作,同时维护 \(dp_i\) 的转移。

需要维护的东西如下:

  • 当前 \(j\)\(dp_{j-1}\),单点修改。
  • 上文提到的操作,区间修改。
  • \(dp_i\) 的转移,同时区间查询最小值。

每次二分找到满足条件的区间右端点的后继 \(ptr\),查询区间即为 \([ptr + 1,i]\),最终答案 \(dp_{n}\),完结撒花!

弔图:

真实反映了数据点之多

全机房都被 POJ 搞红温的一天

12.13

非常好的 %你赛,使我大脑旋转。

\(10 + 30 + 30 + 0 = 70\text{ rank inf }\)

T1 CF864F

你家 \(2700\) 放 T1 (?

假设终点为 \(t\),如果从 \(s\) 可到达 \(t\),那么 \(s \to t\) 的最小字典序路径上的节点,即 \(s\) 的后继 \(s'\) 均是能到达 \(t\) 的最小编号节点,从 \(s\)\(s'\) 连边,全图会构成多个环 + 一棵树,对于环上无法到达 \(t\) 的点,无解,反之,等效于在树上求 \(k\) 级祖先。

考虑离线,答案在 \(\text{dfs}\) 维护一个栈即可。

对于两点之间的连通性可以 \(\text{Tarjan}\) 缩点加拓扑序 \(\text{dp}\),由于只是确认连通性,可以 \(\text{bitset}\) 优化,对于每一节点对应的环,可以并查集维护。

实现只需枚举终点 \(t\),复杂度 \(O(nm + (n^2 + q)\log n)\)

T2

这才是我要的 T1

一开题就想二分,已经被这种式子不知道坑了多少次了,但是它不是,只需要用埃式筛筛出完全平方数,再去找 \(\max(n,m)\) 内是完全平方数因数的数的个数即可。

T3 CF1198B

你家 \(1600\) 放 T3 (?

我被诈骗了

分块,启动!只需要对于整块维护一个 \(tag\),对于操作二将 \(tag_i < x\) 的置为 \(x\),对于操作一更新当前所在块即可,最后再更新一次所有块,复杂度 \(O((q + n)\sqrt{n})\)

CF115E Linear Kingdom Races

\(dp_{i,j}\) 表示前 \(i\) 个当中选了 \(i \sim j\) 的最大利润,得到转移 \(dp_{i,j} = dp_{i-1,j} + value(i,j) - cost_i\)\(dp_{i,j}\) 只会从 \(dp_{i-1,j}\) 转移,删掉第一维,考虑优化,对于 \(value(i,j)\) 维护区间最大,每次转移 \(dp_{i,i}\) 时会对 \([1,i]\) 内的 \(dp\) 值产生一个 \(-cost_i\) 的影响,\(dp_{i,j}\) 亦同,即为区间加,只需维护一个 \(Answer\) 每次更新时同步给区间加上 \(Answer\) 即可,复杂度 \(O(n \log n)\)

[ABC087D] People on a Line

差分约束板子,注意线段矛盾情况。

去触摸

最亮的光

最美的焰火

是岁月

温柔地经过

自由托住我

我去往未知的辽阔

在旅途的 最终点

再见

会再见

—— 张杰/HOYO_MIX《经过》

12.13

昨天体育课踢球 \(20 \sim 25\) 码处爆抽一脚竟然进了 (?

$10 \min $ 内草坪下雨太滑连摔三次给我摔的怀疑人生,但我想说我这个小模型就是好用 awa

P2605 [ZJOI2010] 基站选址

\(dp_{i,j}\) 表示在第 \(i\) 个村庄建立第 \(j\) 个基站不考虑后 \([i + 1,n]\) 个村庄的最小总费用,得到转移:\(dp_{i,j} = \displaystyle\min^{i-1}_{k=1}\{dp_{k,j-1} + cost_{k,i}\}\),其中 \(cost_{k,i}\) 表示对于区间 \([k,i]\) 中没有被基站覆盖的赔偿费用,但是是 \(O(n^2)\),无法接受。

由于跟区间有关,考虑线段树。

定义集合 \(S_i\) 表示能够覆盖 \(i\) 村庄的基站集合,钦定 \(st_i\) 为集合中最大值,\(en_i\) 为集合中最小值,线段树维护转移 \(dp_{i,j} = \displaystyle\min^{i-1}_{k=1}\{dp_{k,j-1} + cost_{k,i}\}\),即维护区间 \(\min\),枚举所建立的基站 \(k\),对于每一层建树,初值 \(dp_{i,j-1}\),若 \(i = en_k\),更新区间 \([1,st_k - 1]\) 加上 \(w_k\),反之则查询区间 \([1,st_k - 1]\),更新答案。

P3188 [HNOI2007] 梦幻岛宝珠

乍一看 \(\text{01}\) 背包板子,再一看 \(W \leq 2^{30}\),我不打扰,我走了哈。

好的我回来了

首先看到数据范围中 \(\forall{w_i}\) 定能拆成 \(a \times 2^b\) 的形式,且 \(a \leq 10,b \leq 30\),很可做,因此考虑将物品按照 \(b\) 分组,将 \(a\) 看做体积,再去做 \(\text{01}\) 背包,记 \(g_{i,j}\) 表示当前组为 \(i\),所选物品总体积为 \(j\) 的价值最大,得到一个转移:

\(\large{g_{i,j} = \displaystyle\max\{g_{i,j - V_{i,j} + val_{i,j}}\}}\)

其中 \(V_{i,j}\) 为预处理的二进制拆分之后的物品体积,\(val_{i,j}\) 为物品输入的价值。

当然,在我们以为这题做完之后,注意力惊人的我们惊人的发现 \(W\) 不满足 \(a \times 2^b\) 的拆分条件,所以还得去处理 \(W\)

\(dp_{i,j}\) 表示前 \(i\) 组一共选了体积为 \(j \times 2^i\) 的物品的最大价值,每次枚举当前第 \(i\) 组选出的体积 \(k\),对于另外枚举的体积 \(j\),在二进制下,\((j - k) \times 2\) 即为在 \(i - 1\) 组选的体积,另外对于每次考虑的 \(W\) 在这一位的影响也要加上,因此得到一个转移:

\(\large{dp_{i,j} = \displaystyle\max\{dp_{i-1,(j - k) \times 2 + W_{i-1}} + g_{i,k}\}}\)

其中 \(W_{i-1}\) 表示 \(W\) 在上一位 \(i - 1\) 在二进制中是 \(0\)\(1\),最终答案即为 \(dp_{size(W) - 1,1}\)\(size(W)\) 即为最多分组数。

12.15

早上睡到 \(10:30\),突然想到有 \(\text{THUPC}\),着急忙慌的起床,打开微信发现已经被两个大神队友消息轰炸了,于是乎 \(11:00\) 比赛开始我先去煮个饺子,回来发现他们发现大部分是不可做题于是去联机战地 \(5\) 去了,打开机房群发现 \(\text{zcy}\) 大佬正在抱怨着赛时玩原神的 \(\text{jmr}\) 以及 \(\text{qcz}\),于是我加入了它们的世界 (乐

吃完下午三点的午饭打了会 \(\text{codm}\) 就回学校了。

12.16

今天的太阳真的真的超级舒服。

UVA11149 Power of Matrix

\(n\) 为偶数

\( \begin{aligned} \displaystyle\sum^n_{k = 1}{A^k} &= A + A^2 + \dots + A^k \\ &= A + A^2 + \dots + A^{\frac{k}{2}} + A^{\frac{k}{2}} \times (A + A^2 + \dots + A^{\frac{k}{2}}) \\ &= A + A^2 + \dots + A^{\frac{k}{2}} + A^{\frac{k}{2}} \times (A + A^2 + \dots + A^{\frac{k}{4}} + A^{\frac{k}{4}} \times (A + A^2 + \dots + A^{\frac{k}{4}})) \end{aligned} \)

\(n\) 为奇数的情况下只需要在加一个 \(A^k\) 即可,因为 \(\frac{n}{2}\) 向下取整,即可分治和矩乘,复杂度 \(O(n^3 \log k)\)

P2886 [USACO07NOV] Cow Relays G

既然 \(T \leq 100\),意识到邻接矩阵当然也是矩阵 (废话,于是去重边后建邻接矩阵矩阵快速幂即可,当然是类似 \(\text{Floyd}\) 的转移。

P5059 中国象棋

对于上下的行是没有干扰的,只需要考虑独立的每一行,显然有 \(dp_{i,0/1}\) 表示当前 \(i\) 是否放卒,有转移:

\[\begin{aligned}dp_{i,1} &= dp_{i - 1,0} \\ dp_{i,0} &= dp_{i - 1,0} + dp_{i - 1,1} \end{aligned} \]

转化成矩阵:

\[\begin{bmatrix} dp_{i,0} \\ dp_{i,1} \end{bmatrix} = \begin{bmatrix} dp_{i-1,0} \\ dp_{i-1,1} \end{bmatrix} \times \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \]

矩阵快速幂优化即可,根据乘法原理可得答案即为每行答案的乘积。

P4159 [SCOI2009] 迷路

对于仅有 \(\text{01}\) 边权的邻接矩阵,我们可以直接快速幂算,但是这题不是,那就考虑一个类似于分层图的东西,把一个点拆成 \(9\) 个点,在每一层之间连一条权值为 \(1\) 的边,再在两层之间连一条边,对新的矩阵进行快速幂。

P6569 [NOI Online #3 提高组] 魔法值

由于原图的边权均为 \(0\)\(1\),因此题面中的式子可以变成:

\( \begin{aligned} f_{x,i} = f_{1,i - 1} \times e_{1,x} \oplus f_{2,i - 1} \times e_{2,x} \oplus \dots f_{n,i - 1} \times e_{n,x} \end{aligned} \)

啊,这不就是异或版矩乘?

记第 \(i\) 天的魔法值矩阵为 \(f_i\),邻接矩阵为 \(e\),由于邻接矩阵有且仅有 \(01\),因此它满足矩乘结合律,得到以下式子:

\( \begin{aligned} f_{i} = f_{i - 1} \times e \to f_i = f_0 \times e^i \end{aligned} \)

如果每次询问都跑一次快速幂一定会爆,因此看到数据范围 \(f_i,a_i < 2^{32}\),不难想到类似状压的方法,预处理邻接矩阵的 \(2^i\) 即可,复杂度 \(O(31(n^3 + q))\)

12.17

T1 HDU 6804

跑两遍 \(\text{01}\) 背包即可。

T2 HDU 6810

组合数学。

对于一条边,边的两边分别有 \(i\)\(m - i\) 个人时,其贡献为 \(\min(i,m - i)\),我们记边两边的子树大小分别为 \(s\)\(n - s\)\(f_s\) 为子树的贡献,得到一个式子:

\( \begin{aligned} f_s &= \displaystyle\sum^{m - 1}_{i = 1}{\binom{s}{i} \cdot \binom{n - s}{m - i} \times \min(i,m - i)} \\ &= \displaystyle\sum^{\lfloor{\frac{m - 1}{2}}\rfloor}_{i = 1}{\binom{s}{i} \binom{n - s}{m - i} \times i + \binom{s}{m - i} \binom{n - s}{i} \times i} \\ &+ [m \text{ mod } 2 = 0] \binom{s}{\frac{m}{2}} \binom{n - s}{\frac{m}{2}} \times \frac{m}{2} \end{aligned} \)

由于 \(\sum\) 里的两个式子具有对称性,可以只考虑一个。

\(p = \lfloor{\frac{m - 1}{2}}\rfloor\),可得:

\( \begin{aligned} g_s &= \displaystyle\sum^p_{i=1}{\binom{s}{i} \cdot \binom{n - s}{m - i} \times i} \\ &= s \times \displaystyle\sum^p_{i=1}{\binom{s - 1}{i - 1} \cdot \binom{n - s}{m - i}} \\ &= s \times h_s \end{aligned} \)

所以只需要知道 \(h_s\),就可以 \(o(1)\)\(f_s\)

\(s = 1\) 时,\(\binom{n - 1}{m - 1}\) 均合法。

考虑 \(s - 1 \to s\) 产生的不合法的方案数,\(h_s\) 可以理解为前 \(s - 1\) 个至多放了 \(p - 1\) 个小球,不合法的方案可以看做 \(s - 1\) 的位置一定放了球,前 \(s - 2\) 中放了 \(p - 1\) 个球,剩下的球便在剩下的 \(n - s\) 个盒子中的方案数。

\(\text{All in all}\),我们只需要预处理 \(h_s\),然后预处理 \(f_s\),在 \(\text{dfs}\) 途中统计 \(u\) 子树大小统计答案即可。

T4 [ARC057D] 全域木

对于 \(\text{Kruskal}\) 的过程,即为从一个连通块跨越至另一连通块的最小边,将其加入生成树中,考虑 \(\text{dp}\),记 \(dp_{U,x,y}\) 表示当前连通块大小可重集 \(U\),考虑到权值为 \(x\) 的边,确定了有 \(y\) 条未被处理的边在加入 \(U\) 后不会改变其连通性的方案数。

\(\Large\text{How to explain that ?}\)

每次我们在合并连通块时,会有许多无用边,意为这些边在加入原集合 \(U\) 中时并不会对 \(U\) 中节点的连通性产生影响,\(U\) 就可以理解为 \(n\) 的整数分拆数,大约是 \(3.8e4\),其中的 \(y\) 就是在一定的时候分去不能选择的 \(x\) 进行操作。

根据 \(x\) 被选或不被选分类:

  • \(x\) 不被选,则 \(x\) 我们不可选,消耗 \(y\) 中的一条边,转移:\(dp_{U,x,y} = y \times dp_{U,x + 1,y - 1}\),乘 \(y\) 意为有 \(y\) 中选择。

  • \(x\) 被选,枚举连通块 \(\{i,j\} \in U\),合并。我们得到 \(x \times y\) 种选边方案,会贡献 \(x \times y - 1\) 条无用边,记可重集 \(V = U - \{i\} - \{j\} + \{i + j\}\),则有转移:\(dp_{U,x,y} = i \times j \times dp_{V,x + 1,y + i \times j - 1}\)

由于常规拓扑 + dp 没有很好的拓扑序,考虑使用记忆化搜索,若 \(y < 0\) 则返回 \(0\),在所有边全部处理完,即 \(x > \frac{n \times (n - 1)}{2}\) 时返回 \(1\)

答案为 \(dp_{\{1,1,\dots,1\},1,0}\)

#include <bits/stdc++.h>
#define int long long 
#define ri register int

using namespace std;
template <typename Tp>
inline void read(Tp &x){
    Tp res(0),f(1);
    char ch = getchar();
    while(!isdigit(ch)){f = ch == '-' ? -1 : 1;ch = getchar();}
    while(isdigit(ch)){res = (res << 1) + (res << 3) + (ch ^ 48);ch = getchar();}
    x = res * f;
}
const int maxn(2333),mod(1e9 + 7);
int n,a,m;
bool flag[maxn];
map <vector<int>,int> dp[maxn][maxn];

int Dfs(vector <int> vec,int x,int y){
    if(y < 0) return 0;
    if(x > m) return 1;
    if(dp[x][y].count(vec)) return dp[x][y][vec];
    if(!flag[x])
        return dp[x][y][vec] = y * Dfs(vec,x + 1,y - 1) % mod;
    int tmp(0),len(vec.size());
    vector <int> tag;
    for(ri i(1);i < len;i ++){
        for(ri j(0);j < i;j ++){
            tag.clear();
            for(ri k(0);k < len;k ++){
                if(k != i && k != j) tag.push_back(vec[k]);
            }
            tag.push_back(vec[i] + vec[j]);
            sort(tag.begin(),tag.end());
            tmp = (tmp + vec[i] * vec[j] % mod * Dfs(tag,x + 1,y + vec[i] * vec[j] - 1) % mod) % mod;
        }
    }
    dp[x][y][vec] = tmp;
    return dp[x][y][vec];
}

inline void Solve(){
    read(n),m = n * (n - 1) / 2;
    for (ri i(1);i < n;i ++) read(a),flag[a] = 1;
    vector <int> Ans(n,1);
    int Answer = Dfs(Ans,1,0);
    printf("%lld\n",Answer);
}

signed main(){
    int T = 1;
    while(T --)
        Solve();
    return 0;
}

12.18

专题在做矩乘优化,写了篇学习笔记: Learning Notes 学习笔记 x 矩乘

P1306 斐波那契公约数

为了证 \(gcd(f_n,f_m) = f_{gcd(n,m)}\) 草稿纸写满了 (证明先欠着

总之就是证出来就可以矩乘加速推斐波那契了。

12.19

上午神奇 %你赛,爆零 rank 7

T1

神奇的树形 dp,使我大脑旋转。

由于题目中有这样一句话:保证所有仇视关系构成一棵树,考虑 dp。

\(dp_{u,i}\) 表示当前 \(u\) 节点的人定制烟花占用时刻 \(i\) 的最小怨气值,有转移:

\( dp_{u,i} = \displaystyle\sum_{v \in son_u,k = 0}^{r_u - l_u - t_u + 1}{\min\{dp_{v,k} + \max\{0,\min\{r_u,r_v\} - \max\{l_u,l_v\} + 1\}\}} \)

答案为 \(\displaystyle\min_{i=0}^{r - l - t + 1}\{dp_{root,i}\}\)

T2

插入删除都可以用 set 维护,重点就是三操作,我们发现对于一个数假设它是 \(k^4\),我们只需要再给它乘两个 \(k\) 就可以使得其变为完全立方数,因此我们发现,对于一个数,我们将其分解质因数,若其幂指数 \(\alpha \bmod 3 = 1\),将 \(x\) 乘上 \(\alpha^2\),若 \(\alpha \bmod 3 = 2\),将 \(x\) 乘上 \(\alpha\),把这些放进一个 checker,每次操作判一下就行。

T3 P7443 「EZEC-7」加边

赛时题面

神奇的树形 dp again,使我大脑不见。

\(dp_{u,0/1}\) 表示使得该点成为先手必胜 / 必败的最小花费。

初始时,$ dp_{i,0} = dp_{i,1} = 0 $。

考虑加边时,若加入边的起点为先手必败点,终点定是先手必败点中权值最小点且为非祖先节点,因为如果连了一条返租边,在 \(\text{Ayaka}\)\(\text{Yoimiya}\) 都使用最优策略时,会陷入每个人下一步都一定最优导致无法决胜负的死循环中,当然,对于起点为先手必胜点,再向外连边并不更优。

对于一个必胜点,它的儿子节点至少有一个必败;否则,若儿子节点均为必胜点,该点为必败点,所以有转移:

\( \begin{aligned} dp_{u,1} &\leftarrow \min\{dp_{v,0}\} \\ dp_{u,0} &\leftarrow dp_{v,1} \end{aligned} \)

在更新树的每一层时向下丢一个这一层的最大值和次大值,另开一个数组记花费,答案即为 \(dp_{root,!fst}\)\(fst\) 表示 \(\text{Ayaka / Yoimiya}\) 先手。

12.20

VP CF Global Round,发挥比平时 div.2 好打了三道题。

赛后发现自己被 D 题诈骗了(乐

Solution for B

Solution for C

Solution for D

Solution for F

又开新专题了 —— 笛卡尔树,好东西。

P6453 [COCI2008-2009#4] PERIODNI

联想到一个非常经典的问题:对于一个 \(n \times m\) 的矩阵,在矩阵中放 \(k\) 个象棋车,使得它们不互相攻击的方案数。

每一行每一列都有 \(k\) 个位置可选且不相同,又及 \(k\) 个车可以任意交换,即全排列,因此,总方案数为 \(\binom{n}{k} \binom{m}{k} k!\)

回到这题我们想到给原图拆分矩阵,建立小根笛卡尔树,对于笛卡尔树的每个节点,我们存每个矩阵的长宽,考虑记 \(dp_{u,i}\) 表示以 \(u\) 为根的子树中放了 \(i\) 个不同的数的方案数,可以发现,把 \(k\) 当做背包的容积,这个问题就变成了一个树上背包问题,但是发现直接乱转移会多一个 \(\sum\),干脆先预处理 \(u\) 的子树(不包含 \(u\)),新记 \(g_{u,i}\) 表示 \(u\) 的子树中放了 \(i\) 的方案数,有转移:

\( \begin{aligned} g_{u,i} &= \displaystyle\sum_{j=0}^{i}{dp_{lson,j} \times dp_{rson,i - j}} \\ dp_{u,i} &= \displaystyle\sum_{j=0}^{i}{\binom{h_u}{j}\binom{h_w - j}{i - j} (i - j)! \cdot g_{u,j}} \end{aligned} \)

答案即为 \(dp_{root,k}\)

12.21 & 12.22

摆。

学校给其它初三周四放,我们机房周六放,导致回家怨气较重,因而 codm 三连跪(服了

12.23

CF2049D Shift + Esc

暴力做法是一个神秘的 dijkstra,先记题目中给的 \(k\)\(\lambda\),记 \(dis_{i,j,k}\) 表示走到 \((i,j)\) 时在第 \(k\) 列的最小花费,我们连边 \((i,j,k) \xrightarrow{a_{i,j \bmod m + 1}} (i,j \bmod m + 1,k + 1),(i,j,k) \xrightarrow{\lambda (j + m - k) \bmod m} (i + 1,j,k) + a_{i + 1,j} (j \in [1,m])\),当然这样是 \(O(nm^2 \log(nm^2))\),接受不了。

发现每次转移 \((i,j,k) \to (i + 1,j,k)(j \in [1,m])\) 可以预处理,因为 \(dis_{i + 1,j,k} = \min\{dis_{i,j,k} + \lambda(j + m - k) \bmod m\}\),另外开个数组处理即可。

答案即为 \(\displaystyle\sum_{i=1}^{m}{\min\{dis_{n,i,m}\}}\)

CF2049E Broken Queries

神秘交互题。

首先是去判断 \(k\)\(\frac{n}{2}\) 的关系,询问 \([1,\frac{n}{4}] \oplus [\frac{n}{4} + 1,\frac{n}{2}]\),判断其是否与 \([1,\frac{n}{2}]\) 的答案相同,若相同,\(1\)\([\frac{n}{2} + 1,n]\) 中,反之在 \([1,\frac{n}{2}]\) 中,然后二分去找即可。

HD0X 同学说我日记像每日题解,当然其实就是每天的做题思路 + 过程回顾,毕竟 I'm vegetable,当然多写点闲话之类的也可以。

12.24

上午 %你赛,终榜 \(100 + 0 + 0 + 0 = 100\)

T1 P9186 [USACO23OPEN] Milk Sum S

对于每次 \(a_x = y\) 的操作,我们找到第一个大于等于 \(y\)\(a_i\) 的位置 \(ptr\),如果 \(ptr > x\),对于所有排序后 \([x + 1,ptr - 1]\) 的位置答案会减掉这一段的和一次;\(ptr < x\) 的情况同理;\(ptr = x\) 的情况,就是将 \(a_x\) 替换为 \(y\) 即可,这里 \(a_x\) 的位置是指排序后 \(a_x\) 的位置,所以维护一个后缀一个前缀,每次操作二分找即可,复杂度 \(O(q \log n)\)

T2 P9017 [USACO23JAN] Lights Off G

神秘状压。。。。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int maxn = (1LL << 20) + 10;
int T, n, state[maxn], dp[45][maxn];
string str1, str2;

inline int getState (int x) { return (x >> 1) + ((x & 1) << (n - 1)); }

signed main() {
    cin.tie (0) -> sync_with_stdio (0);
    cout.tie (0) -> sync_with_stdio (0);
    memset (state, -1, sizeof state);
    cin >> T >> n;
    for (int i = 0, ptr = i; i < (1LL << n); i ++, ptr = i) {
        while (state[ptr] == -1) state[ptr] = i, ptr = getState (ptr);
    }
    dp[0][0] = 1;
    for (int i = 1, x = 0; i <= (n << 1); i ++) {
        x ^= (1 << ((i - 1) % n));
        for (int j = 0; j < (1LL << n); j ++) dp[i][state[j]] |= dp[i - 1][state[j ^ x]];
    }
    while (T --) {
        cin >> str1 >> str2;
        int tmpA = 0, tmpB = 0;
        for (int i = 0; i < n; i ++) 
            tmpA |= (str1[i] - '0') << (n - i - 1), tmpB |= (str2[i] - '0') << (n - i - 1);
        if (!tmpA) {
            cout << "0" << endl;
            continue;
        }
        for (int i = 1; ; i ++) {
            tmpA ^= tmpB, tmpB = getState (tmpB);
            if (dp[i][state[tmpA]]) {
                cout << i << endl;
                break;
            }
        }
    }
    return 0;
}

T3 P10280 [USACO24OPEN] Cowreography G

一、首先我们明确一个东西:对于位置 \(i,j (i < j)\),从 \(i \to j\) 的最小花费为 \(\lceil{\frac{j - i}{k}}\rceil\),这很显然。

二、接下来是贪心策略的证明,第一个我们要证明:对于需要操作的串 \(s\) 中的所有的 \(1\),我们定能将其分成若干段,使得其全部向左 / 右匹配,这样是最优的。

转化到数学上来,若存在 $c > a > b 且 \(a > d\) (或 \(c < a < b\) 且 $ a < d $ 同理),我们只会匹配 \((c,a)\)\((b,d)\),这样是最优的,即我们不选择交叉匹配。

以下分讨:

  • \(1^0 \enspace c > a > b > d\)

    无交叉配对的情况 \((1)\)\(\lceil{\frac{c - a}{k}}\rceil + \lceil{\frac{b - d}{k}}\rceil\)

    交叉配对的情况 \((2)\)\(\lceil{\frac{c - b}{k}}\rceil + \lceil{\frac{a - d}{k}}\rceil\)

    显然 \((1) \leq (2)\)

  • \(2^0 \enspace c > a > d > b\)

    同理可得。

综上可得,在每段中有 \(sum_s \geq sum_t\)\(t\) 为匹配串)或者 \(sum_t \geq sum_s\),即全向左 / 右匹配。

三、假设全部向右匹配。

\(s_i,t_i\) 表示该段内 \(1\) 的下标,\(p(i)\) 为排列,\(\{x\}\) 表示 \(x\) 的分数部分,\(\{x\} = x - \lfloor{x}\rfloor\)

可以得到花费的表达式:

\[\displaystyle\sum^n_{i=1}{\lceil{\frac{t_{p(i)} - s_{i}}{k}}\rceil} = -\displaystyle\sum^n_{i=1}{\lfloor{\frac{s_{i} - t_{p(i)}}{k}}\rfloor} \]

\(x_i = \frac{s_i}{k},y_i = -\frac{t_{p(i)}}{k}\),原式变成:

\[\displaystyle\sum^n_{i=1}{\lfloor{x_i + y_{p(i)}}\rfloor} = \displaystyle\sum_{i=1}^n{x_i} + \displaystyle\sum_{i=1}^n{y_i} - \displaystyle\sum_{i=1}^{n}\{x_i + y_{p(i)}\} \]

我们需要最小化:\(\displaystyle\sum_{i=1}^{n}\{x_i + y_{p(i)}\}\)

\(x_i' = \{x_i\},y_i' = \{y_i\}\)

\[\displaystyle\sum^{n}_{i=1}\{x_i + y_{p(i)}\} = \displaystyle\sum^n_{i=1}{x_i'} + \displaystyle\sum^n_{i=1}{y_i'} - \displaystyle\sum^n_{i=1}{[x_i' + y_{p(i)}' \geq 1]} \]

方括号为艾弗森括号,成立为 \(1\),不成立为 \(0\)

我们需要最大化:\(\displaystyle\sum^n_{i=1}{[x_i' + y_{p(i)}' \geq 1]}\)

\[\begin{aligned} \displaystyle\sum^n_{i=1}{[x_i' + y_{p(i)}' \geq 1]} =& \displaystyle\sum_{i=1}^n{[\frac{s_i}{k} + (- \frac{t_{p(i)}}{k}) \geq 1]} \\ =& \displaystyle\sum_{i=1}^n{[\frac{s_i}{k} \bmod 1 + (- \frac{t_{p(i)}}{k}) \bmod 1 \geq 1]} \\ =& \displaystyle\sum_{i=1}^n{[s_i \bmod k + (- t_{p(i)}) \bmod k \geq k]} \end{aligned} \]

由此得出我们需要最大化余数相加 \(\geq k\) 的对数,因此去匹配第一个 \(s_i \bmod k \geq t_i \bmod k\),若没有就匹配最小的 \(s_i \bmod k\),这样就是最优的。

因此只需要用 set 神秘维护即可。

#include <bits/stdc++.h>
#define int long long

using namespace std;
int n, k, Answer;
string str1, str2;
multiset < pair<int,int> > s[2];
signed main() {
    cin.tie (0) -> sync_with_stdio (0);
    cout.tie (0) -> sync_with_stdio (0);
    cin >> n >> k >> str1 >> str2;
    str1 = " " + str1, str2 = " " + str2;
    for (int i = 1; i <= n; i ++) {
        if (str1[i] != str2[i]) {
            int tmpX = str1[i] - '0';
            if (s[!tmpX].empty()) s[tmpX].insert ({ i % k, i });
            else {
                multiset < pair<int,int> > :: iterator iter = s[!tmpX].lower_bound ({ i % k, 0 });
                if (iter == s[!tmpX].end()) iter = s[!tmpX].begin();
                Answer += (i - (iter -> second) - 1) / k + 1, s[!tmpX].erase (iter);
            }
        }
    }
    cout << Answer << endl;
    return 0;
}

12.25

开始学神秘二项式反演和容斥了,使我大脑旋转,过段时间一定是会写篇笔记的,等我学懂再说吧(

发现好像找到题目中符合二项式反演的关系就可以很快解决捏。

P10596 BZOJ2839 集合计数

\(f_i\) 表示集合交集元素恰为 \(i\) 的方案数,\(g_i\) 表示选出 \(j\) 个子集交集元素为 \(i\) 的方案数,发现它们满足二项式反演的关系,即 \(f_i\)\(i\) 个不同元素构成特定结构的方案数时 \(g_i\) 是从 \(i\) 个中选出 \(j \geq 0\) 个构成特定结构的方案数,\(f_k\) 即为我们想要的答案,得到以下柿子:

\[g_n = \displaystyle\sum_{i=0}^{n}{\binom{n}{i}f_i} \\ \downarrow \\ f_n = \displaystyle\sum_{i=0}^{n}{(-1)^{n-i}\binom{n}{i}g_i} \]

所以推出 \(f_k\) 的柿子,但是在 \(n - k\) 中取会算重,所以应该是取至少 \(k\),得到:

\[f_k = \displaystyle\sum^{n}_{i=k}{(-1)^{i-k}\binom{i}{k}g_i} \]

在剩下 \(n - k\) 个中,选的方案数 \(\binom{n}{i}\),任意选或不选,方案数是 \(2^{n-k}\),再从这些集合中选出若干个非空集合方案数是 \(2^{2^{n-k}} - 1\),得到 \(g_i\)

\[g_i = \displaystyle\sum^{n}_{i=k}{\binom{n}{i} \cdot (2^{2^{n-i}} - 1)} \]

计算 \(2^{n-i}\) 时根据进食后人帖,需要模 \(mod - 1\),可以根据扩欧得到(不会),不然就是 \(70 pts\)

12.26

上午 %你赛,\(60 + 20 + 30 + 0 = 110pts\),T3 推柿子 \(\prod\) 写成 \(\sum\),警钟长鸣。

T1

赛时手玩数据得出结论:选前缀 or 后缀都不优,整段最优,然后被第二个大样例叉掉,遂打暴力。

选前缀是最优的,二分找第一个大于的即可。

T2

数据范围 \(n \leq 23\),显然状压,赛时想了个神秘的 \(O(n^5)\)区间 dp,成功自己叉掉这个做法。

区间操作转化为差分数组操作,每次枚举状态 \(st\) 中的每一项取 \(\max\),如果当前状态和为零 \(dp_{st} + 1\),答案 \(n - dp_{2^n - 1}\)

T3

最唐的一集,组合数学,答案:\(\displaystyle\prod_{i=1}^{n}{\binom{c_{i+1} + 1}{c_i}}\),wssb

唐完了。。。。。。

P6521 [CEOI2010 day2] pin

看到恰有就想到二项式反演,于是打算定义一个至多一个恰好。

恰有 \(d\) 个对应位置字符不同 \(\to\) 恰有 \(4 - d\) 个对应位置字符相同。

\(g_k\) 表示至多有 \(k\) 个对应位置字符相同,\(f_k\) 表示恰有 \(k\) 个对应位置字符相同。

\[g_k = \displaystyle\sum_{i=0}^{k}{\binom{k}{i}f_i} \Rightarrow f_k = \displaystyle\sum^{k}_{i=0}{(-1)^{k-i}\binom{k}{i}g_i} \]

答案即为 \(f_d\),计算 \(g_i\) 用哈希即可,开 map 记哈希值出现次数。

不要忘记我,我真的不想被忘记啊…… —— nan

12.27

马蜂良好,已经成压行仙人了。

P2567 [SCOI2010] 幸运数字

dfs 筛幸运数字,暴力处理倍数,考虑容斥去除重复的 \(\operatorname{lcm}\),对于 \(x \in [A,B]\),其倍数个数为 \(\lfloor{\frac{B}{x}}\rfloor - \lceil{\frac{A}{x}}\rceil + 1\),每次依旧是 dfs 统计答案,但是会 T,需要剪枝,当然明显的,当前 \(\operatorname{lcm} > B\) 一定不行,对于预处理出的倍数,从大到小搜肯定会更优,因为会更快超越右端点,对于 \(a \mid b\) zhong \(b\) 一定无贡献,直接踢掉。

P8737 [蓝桥杯 2020 国 B] 质数行者

正常 dp 思路实现是 \(O(n^3 cnt)\),其中 \(cnt\) 为质数个数,结合容斥我们可以从前两维向第三维合并,具体一点:

\[\begin{aligned} dp_{i,j,k} &= \displaystyle\sum^{n}_{i=0}\displaystyle\sum^{m}_{j=0}\displaystyle\sum^{w}_{k=0}{dp_{i - prime_t,j,k} \times dp_{i,j - prime_t,k} \times dp_{i,j,k - prime_t} \times \frac{(i + j + k)!}{i! j! k!}} \\ \Downarrow \\ dp_{i,j} &= \displaystyle\sum^{n}_{i=0}\displaystyle\sum^{m}_{j=0}{dp_{i - prime_t,j - 1}} \\ g_{i + j} &= \displaystyle\sum^{n}_{i=0}\displaystyle\sum^{m}_{j=0}{dp_{n,i} \times dp_{m,j} \times \frac{(i + j)!}{i!j!}} \\ tmpSum &= \displaystyle\sum^{n + m}_{i=0}\displaystyle\sum^{w}_{j=0}{g_i \times dp_{w,j} \times \frac{(i + j)!}{i!j!}} \end{aligned} \]

答案需要容斥计算: 从 \((1,1,1) \to (n,m,w) - \sum (1,1,1) \to (r_i,c_i,h_i) \to (n,m,w) + (1,1,1) \to (r_1,c_1,h_1) \to (r_2,c_2,h_2) \to (n,m,w) + (1,1,1) \to (r_2,c_2,h_2) \to (r_1,c_1,h_1) \to (n,m,w)\)

P5123 [USACO18DEC] Cowpatibility G

对于每个口味二进制存,总对数为 \(n \times (n - 1)\),去计算能够和谐共处的对数,vector 存每种答案的状态,可能会算重,需要容斥。

P8315 [COCI2021-2022#4] Šarenlist

Solution

12.29

终于等到周末了(

前一天晚上肝原神肝到 1:30,今天睡到整整 10:00 吃了早饭就开始练字 + 读书 + 看手机,下午打了几把 codm 排位 + 三角洲,发现我方队友占点后不守点,疑似唐诗。

晚上返校。

Good Bye 2024 没打,开始 vp,顺利切掉 A,开始胡思乱想 B,发现没看到 \(l_i \leq r_i\),以为不能相等原地爆炸,吃了几发切掉,然后发现自己没开 vp,干脆不开了(乐。

天真的按照 T3 题意暴搜,直接 T 飞,推出是结论,切掉。

后面直接开摆,不是昨天洗的头今天怎么又这么油。。。

12.30

跨年倒计时 2d

被容斥薄纱了。 被数位 dp 薄纱了。 被机房所有人薄纱了。

……

CF1400G

区间的处理可以差分,注意到 \(m \leq 20\),考虑状压,对于一个状态 \(st\),它在二进制下当前位为 \(1\) 表示需要选当前位,\(0\) 反之,然后就是容斥:至少 \(0\)\(1\) \(-\) 至少 \(1\)\(1\) + 至少 \(2\)\(1\) \(\cdots\),以上均讨论方案数。

接着是动规,记 \(dp_{i,j}\) 表示前 \(i\) 个选 \(j\) 人出征的方案,转移方程:

\[dp_{i,j} = \displaystyle\sum^{2m}_{j=0}{dp_{i-1,j} + \binom{t_i - j}{i - j}} \]

\(t\) 为给区间开的桶。

接着找到对于冲突的 \(a_i,b_i\) 的最大 \(l\) 和最小 \(r\),答案:

\[\displaystyle\sum^{2^m - 1}_{st=0}{(-1)^{popcount(st)} \times (dp_{mnr,tmpCnt} - dp_{mxl - 1, tmpCnt})} \]

其中 \(tmpCnt\) 为开桶统计的出现的不同的人的个数。

晚上班上元旦晚会,玩嗨了,唱七里香也唱破音了。。。

变声期的神秘嗓音和一上台就不稳的高音,我真服了 /xk

Good Bye 2024!

Hello 2025!

posted @ 2024-12-23 21:15  Alec_Ayaka  阅读(25)  评论(0编辑  收藏  举报