2024-10-23

传送门

A 简单dp

题意简述
在笛卡尔平面上有 \(n\) 个点,初始时你在原点,然后每次只能走到自己的右上方,在开始前你可以把所有点绕着原点旋转某个角度,求能走的最多的点
\(n\le 50\)

容易发现一个性质是:任何一种合法的方案,任意相邻两个点的连线的斜率都是正的,把这些斜率中的最小值取出来,假设为 \(k\) ,那么把所有的点都旋转顺时针 \(\theta=\arctan(k)\) 度,把那个斜率最小的线段转成水平,那么这个方案仍然合法

这就意味着,任何一种可能能成为答案的方案,一定存在某个 \(\theta\) 使得旋转 \(\theta\) 度后,这个方案仍然合法且其中的某条线段水平,于是 \(\mathcal O(n^2)\) 枚举使哪一对点对的连线水平,然后再 \(\mathcal O(n^2)\) 暴力跑 LIS,因此时间复杂度是 \(\mathcal O(n^4)\) ,平面旋转要注意精度

B

暂时不会

C

简单签到题,被 fyc 秒了,听说是什么放缩

D 简单莫反

题意简述
两个长度为 \(n\) 的序列 \(\{a_n\},\{b_n\}\) ,求 \(\sum_{i=1}^n\sum_{j=1}^n |a_i-a_j|\frac{b_i\cdot b_j}{\gcd(b_i,b_j)^2}\)
$n,a_i,b_i\le 2e5 $

大力推式子,需要用到前置知识:\(1*\mu=\epsilon\)
首先为处理绝对值,按照 \(a_i\) 从小到大排序,得到 \(ans=2\sum_{i\le n}\sum_{j<i}(a_i-a_j)\frac{b_i\cdot b_j}{\gcd(b_i,b_j)^2}\)
需要处理 \(\gcd(b_i,b_j)^2\) 这一项,注意到性质:\(t|gcd(b_i,b_j)\iff \substack{t|b_i\\t|b_j}\)
于是有等式:令 \(f(n)=\frac 1 {n^2}\) ,则有 \(\frac 1 {\gcd(b_i,b_j)^2}=f(\gcd(b_i,b_j))=\sum_{t|gcd(b_i,b_j)}[\mu*f](t)=\sum_{\substack{t|b_i\\t|b_j}}[\mu * f](t)\)
然后将上式代入到答案中得到 \(ans=2\sum_{i\le n}\sum_{j<i}(a_ib_ib_j-a_jb_ib_j)\sum_{\substack{t|b_i\\t|b_j}}[\mu * f](t)\)
然后将 \(t\) 提前,得到 \(ans=2\sum_{i\le n}\sum_{t|b_i}[\mu*f](t)\sum_{\substack{j<i\\t|b_j}}(a_ib_ib_j-a_jb_ib_j)\)
后面那坨东西用两个数组来维护就行了, 具体的
sum1[n] 表示当前加入的 \(b_j\) 中是 \(n\) 的倍数的 \(b_j\) 的和,
sum2[n] 表示当前加入的 \(b_j\) 中是 \(n\) 的倍数的 \(a_j\cdot b_j\) 的和,
然后 \([\mu*f]\) 是可以预处理出来的,统计答案时只需要枚举因子即可,时间复杂度为 \(\mathcal O(160n)\),其中 \(160\) 是最大因子个数

E 简单状压dp

把子序列自动机建出来,然后令 \(f[s]\) 表示能凑出 \(s\) 的最靠前的位置是谁,然后就做完了
时间复杂度 \(\mathcal O(2^a\cdot a + n\cdot a)\)

F 有趣的树形dp

题意简述
给你一棵大小为 \(n\) 的树,然后进行 \(q\) 次询问,每次询问 \(m\) ,使得断掉若干个边,使得每个连通块的结构都是一个圆方树,且这些与这些连通块同构的园方树的割点的总和恰好为 \(m\) ,求方案数

首先题意给了一段代码,你需要看出来这是在求广义圆方树,然后圆方树的性质就要求:
(1)若根节点的度数为 \(1\) ,那么所有叶子节点的深度为为奇数(根节点深度为 \(1\));若根节点度数不为 \(1\) 那么要求所有叶子节点的深度的奇偶性相同
(2)然后把所有度数为 \(1\) 节点染成黑色,接着黑白交替染色,所有度数不为 \(1\) 的黑色节点就是割点

然后你就可以做一个树上背包了,具体的就是 \(f[i][j][0/1][0/1/2]\) 表示以 \(i\) 为根的子树里,割了若干条边,除了 \(i\) 所在的连通块之外都是圆方树,且总共有 \(j\) 个割点,然后 \(i\) 所在的连通块的根节点的奇偶情况为 \(0/1\) ,然后 \(i\)\(0/1/2或以上\) 个儿子,有多少方案数

然后这题 tmd 卡空间,我写了 3.5KB 代码才发现,草泥马
粘一下 MLE 代码

#include <bits/stdc++.h>
#define mpr make_pair
#define int long long
#define pb push_back
#define pii pair<int,int>
#define st first
#define nd second
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())f^=ch=='-';
    for(;isdigit(ch);ch=getchar())x=x*10+(ch^48);
    return f?x:-x;
}
mt19937 rnd(time(0));
const int mo=1e9+7;
inline int qpow(int x,int t){
    int ret=1;
    for(;t;t>>=1,x=x*x%mo)if(t&1)ret=ret*x%mo;
    return ret;
}
inline void red(int &x){x>=mo?x-=mo:0;}
inline void chmin(int &x,int y){x=min(x,y);}
inline void chmax(int &x,int y){x=max(x,y);}

const int N=5e3+5;
int dp[N][N][2][3],f[N][2][3],g[N][2][3],siz[N],dep[N],n,q;
vector<int> edge[N];
void dfs(int u,int fa){
    dep[u]=!dep[fa];
    for(int v:edge[u])if(v!=fa)dfs(v,u);
    memset(f,0,sizeof(f));
    f[0][0][0]=f[0][1][0]=1;
    for(int v:edge[u])if(v!=fa){
        memset(g,0,sizeof(g));
        for(int col=0;col<2;++col)for(int son=0;son<=2;++son){
            for(int x=0;x<2;++x)for(int y=0;y<=2;++y){
                for(int i=0;i<=siz[u];++i)for(int j=0;j<=siz[v];++j){
                    //不连接
                    if(y==1){
                        if(dep[v]==x)
                        red(g[i+j][col][son]+=f[i][col][son]*dp[v][j][x][y]%mo);
                    }
                    else{
                        red(g[i+j][col][son]+=f[i][col][son]*dp[v][j][x][y]%mo);
                    }
                    //连接!
                    if(x!=col)continue;
                    int nxs=min(son+1,2ll),k=j+1;
                    if(y==1){
                        if(dep[v]==x)
                        red(g[i+k][col][nxs]+=f[i][col][son]*dp[v][j][x][y]%mo);
                        else
                        red(g[i+j][col][nxs]+=f[i][col][son]*dp[v][j][x][y]%mo);
                    }
                    else{
                        red(g[i+j][col][nxs]+=f[i][col][son]*dp[v][j][x][y]%mo);
                    }
                }
            }
        }
        siz[u]+=siz[v];
        memcpy(f,g,sizeof(f));
    }
    for(int i=0;i<=siz[u];++i){
        for(int son=1;son<=2;++son)for(int col=0;col<2;++col){
            int rr=i;
            if(dep[u]==col&&col==2)++rr;
            red(dp[u][rr][col][son]+=f[i][col][son]);
        }
        red(dp[u][i][dep[u]][0]+=f[i][dep[u]][0]);
    }
    ++siz[u];
}
void solve(){
    n=read(),q=read();
    for(int i=1;i<=n;++i){
        memset(dp[i],0,sizeof(dp[i]));
        siz[i]=dep[i]=0;
        edge[i].clear();
    }
    for(int i=1;i<n;++i){
        int x=read(),y=read();
        edge[x].pb(y);
        edge[y].pb(x);
    }
    dfs(1,0);
    // for(int i=1;i<=n;++i,puts(""),puts("")){
    //     printf("now print out the statement of node No.%lld\n",i);
    //     for(int j=0;j<=siz[1];++j,puts(""))for(int x=0;x<=1;++x,puts(""))for(int y=0;y<=2;++y){
    //         printf("dp[%lld][%lld][%lld][%lld] = %lld , ",i,j,x,y,dp[i][j][x][y]);
    //     }

    // }
    while(q--){
        int t=read();
        if(t>=siz[1])puts("0");
        else{
            int ans=0;
            for(int x=0;x<2;++x)for(int y=0;y<=2;++y){
                if(y==1){
                    if(dep[1]==x)
                    red(ans+=dp[1][t][x][y]);
                }
                else{
                    red(ans+=dp[1][t][x][y]);
                }
            }
            printf("%lld\n",ans);
        }
    }
    return;
}
signed main(){
    // freopen("test.out","w",stdout);
    for(int cas=read();cas--;)solve();
    return 0;
}

G

H 简单dp

题意简述
给你一个串 \(s\) ,然后你要求有多少个长度为 \(n\) 的字符串,满足 \(s\) 作为子串,不相互重叠出现在 \(s\) 中的次数恰好为 \(k\)
\(|s|\le 100,k\le 10,n\le 1e4\)

考虑对于一个串 \(t\) ,你怎么统计 \(s\) 不相互重叠的出现次数:贪心即可
具体的,你对于每个位置求出 \(x_i\) ,表示当前位置贪心的匹配的情况下,匹配到了 \(s\) 中的哪个位置
然后每次一旦 \(x_i==|s|\) ,将 \(x_i\) 清零
然后就可以 dp 了,令 \(f[i][j][k]\) 表示填了前 \(i\) 个字符,然后通过这个贪心策略,目前位置的 \(x_i\)\(j\) ,且以前出现过 \(k\) 次子串的方案数是多少,预处理一下 kmp 即可,时间复杂度是 \(\mathcal O(26n|s|k)\)

I 傻逼模拟题

J 简单排列问题

题意简述
两个长度为 \(n\) 排列 \(A,B\) 然后称一个自序列是好的,当且仅当 (1)这个子序列的值域连续 (2)这个子序列同时出现在 \(A,B\) 当中,求这个子序列个数
\(n\le 1e6\)

不难发现 (2) 的限制实际上比 (1) 好处理许多,从 (2) 出发来思考问题
而且一个排列的值域连续的子序列可以通过划定这个子序列的最大值和最小值来唯一确定,因此把这样一个子序列记为 \((l,r)\)
不难发现性质:若 \((l,r)\) 是好的,那么 \((l,r-1)\)\((l+1,r)\) 也是好的
因此考虑双指针和 set 来维护,
记录 \(f[l]\) 表示最小值为 \(l\) 的情况下 \(r\) 最大能去到哪,那么显然有 \(f[x-1] \le f[x]\)
具体的,用两个 set 来维护 \(A,B\) 中的子序列的情况,从大往小枚举 \(l\) ,之后查看能不能把 \(l\) 加入到子序列中,即两个检查 set 前驱后继是否分别相等,如果不等就把 \(r\) 给扔了,然后继续检验,时间复杂度是 \(\mathcal O(n\log n)\)

K 简单最段路

题意简述
一张无向图,有 \(n\) 个点,有 \(m\) 个边,边有权,点有权,你需要对于每个点 \(i\) 求出 \(\min\{dist(i,j)+a[j]\}\) ,然后走的过程中允许有一次不花费边权
\(n,m\le 5e5\)

比赛时唐了,想得太复杂了,其实只要建个超级汇点然后跑 dijkstra 即可
考虑分层图,两层图的情况是一致的,然后从一层到二层的边的权值为 0 ,然后二层的每一个点都向超级汇点连一个边权为 \(a_i\) 的边,然后从汇点倒过来跑最短路即可

posted @ 2024-10-24 16:41  chx#XCPC  阅读(41)  评论(1)    收藏  举报