2019.3.1
T1
题意
一个长度为\(m\)的环,环上有\(m\)个等距离的点,标号为0 ... M - 1。环上有\(n\)个线段\((a_i, b_i)\),意义为从点\(a_i\)顺时针延伸到\(b_i\)的线段。
求最多能选出多少个不相交的线段,其中每条线段的端点允许重合。
题解
如果不是环就很好搞了,可以贪心的做。每次选不与当前线段相交的线段中,右端点最靠左的线段就可以保证最优了。也就是下一条选择的线段的左端点要大于等于上一条选择线段的右端点。
那么有环怎么办?
还是考虑贪心。
由上面的贪心策略我们可以知道,当我们选择了一条线段\(i\)后,下一条会选择的线段是确定的。因此我们令\(Next[i]\)表示选择了第\(i\)条线段后,下一条会选择哪条线段。
然后我们拆环成链,枚举每一条线段作为第一条选择的线段,那么最后一条选择的线段右端点肯定要小于等于枚举线段的左端点 + m。
因此我们所要求的就变成了,从1条线段出发,走若干步,在到达地点小于某个值的情况下,求最多能走多少步。
于是用倍增处理一下\(Next\)数组即可。
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define LL long long
#define AC 201000//因为把边复制了,所以要开2倍空间
int n, m, ans;
int f[AC][19], Next[AC], bits[100];
struct road {int l, r;}way[AC];
inline int read()
{
int x = 0;char c = getchar();
while(c > '9' || c < '0') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x;
}
inline void upmin(int &a, int b) {if(b < a) a = b;}
inline void upmax(int &a, int b) {if(b > a) a = b;}
inline bool cmp(road a, road b) {return a.r < b.r;}
void pre()
{
m = read(), n = read();
for(R i = 1; i <= n; i ++)
{
way[i].l = read(), way[i].r = read();
if(way[i].l <= way[i].r)
way[n + i].l = way[i].l + m, way[n + i].r = way[i].r + m;
else way[i].r += m, way[n + i].l = way[i].l + m, way[n + i].r = way[i].r + m;
}
n <<= 1, bits[0] = 1;//所以线段数量就翻倍了
for(R i = 1; i <= 20; i ++) bits[i] = bits[i - 1] * 2;
}
void build()//先找到每条线段下一条是哪一条,然后建出倍增数组
{
sort(way + 1, way + n + 1, cmp);
for(R i = 1, j = 1; i <= n; i ++)
{
while(way[j].l < way[i].r && j <= n) ++ j;
if(way[j].l >= way[i].r) f[i][0] = j;//允许端点重合,所以这里是>=
}
/*int tmp = 1, cnt = 0;
for(R i = 1; i <= n; i ++)
{
if(tmp << 1 == i) tmp <<= 1, ++ cnt;
p[i] = tmp, t[i] = cnt;
}*/
for(R i = 1; i <= 18; i ++)
for(R j = 1; j <= n; j ++)
f[j][i] = f[f[j][i - 1]][i - 1];
}
void work()
{
for(R i = 1; i <= n; i ++)//枚举每一条线段作为开头
{
int rnt = 1, now = i, l = way[i].l, r = l + m;//获取当前枚举的这个区间
for(R j = 18; j >= 0; j --)
{
if(!f[now][j]) continue;//特判掉没有可以选择的
if(way[f[now][j]].r <= r) now = f[now][j], rnt += bits[j];
}
upmax(ans, rnt);
}
printf("%d\n", ans);
}
int main()
{
freopen("in.in", "r", stdin);
pre();
build();
work();
fclose(stdin);
return 0;
}
T2
题意
有一棵\(n\)个节点的树,有\(k\)个人,每个人选择一条路径。一种方案合法当且仅当不存在一条边恰好被\([2, k - 1]\)条路径经过。求方案数。
题解
生成函数 + 分治FFT。然而我并不会,也许以后再填坑?
T3
题意
\[f(n) = \begin{cases} 1 \quad n = 1 \\ f(n - f(f(n - 1)) + 1 \quad n > 1 \end{cases}\]
\[g(n) = \sum_{i = 1}^nf(i)\]
\[h(n) = h(g(f(n))) - f(f(n)) + g(g(n))\]
\(T\)组数据,每组给定一个\(n\).求\(h(n)\)
\(T \le 5, n \le 1e9\)
题解
看上去就是一道数论神题。。。
然而……打表神题。
首先对\(f(i)\)打表,大概长这样:
1 2 2 3 3 4 4 4 5 5 5 6 6 6 6.......
可以发现$f(i) = \(序列中\)i\(的出现次数。(此处的序列指\)f\(数组无限延伸下去构成的序列) 假设已知前2项的值,同时知道这是一个单调不降的序列,那么可以构造出这个序列。 然后暴力计算出\)f$的前\(1e6\)项。然后发现它至少已经确定了\(f\)的前\(1e9\)项了。
然后分析\(h\)中各部分的含义。
\(f(f(n))\)表示序列中为\(f(n)\)的出现次数.
\(g(f(n))\)表示序列中最大的,等于\(f(n)\)的位置
那么\(g(f(n)) - f(f(n))\)则表示序列中第一个等于\(f(n)\)的位置的前一个位置,即最后一个为\(f(n) - 1\)的位置,即\(g(f(n) - 1)\)
~~ 貌似看到这里就看不懂了诶~~
所以\(h(n)\)是之前那一段段连续的数的结尾位置的\(g(g(n))\)之和,那些结尾的位置可以用\(f\)算出,所以只需要考虑\(g(g(n))\)怎么求。
\[g(g(n)) - g(g(n - 1)) = \sum_{i = g(n - 1) + 1}^{g(n)} f(i)\]
\[= \sum_{i = g(n - 1) + 1}^{g(n)} n\]
\[ = n \cdot f(n)\]
所以\(g(g(n)) = \sum_{i = 1}^n i \cdot f(i)\)
然后可以分段计算。
2019.3.8
T1
题意
给定n个数(n为偶数),从中选取\(\frac{n}{2}\)个数,使得选中数的最大公约数最大。
题解
因为要选\(\frac{n}{2}\)个数,所以每次随机一个数,有\(\frac{1}{2}\)的概率选到一个在最优答案集合中的数,那么我们枚举这个数的所有约数,对于每个约数check一下就好了。
T2
题意
一个\(n * 6\)的矩阵,在每个格子里填上\(x \in [1, 6]\),要求任意数与左,右,左上,左下,右上,右下的数字既不能相同,也不能和为7.
且左上角必须为1,且按照从上往下逐行再从左往右的顺序,第一个2必须要出现在5的前面,第一个3必须要出现在4的前面。
求方案数, 对 1004535809 取模。
题解
,,,并不是很懂
题解说先搞个暴力DP打表,然后用高斯消元or拉格朗日插值找规律。最后发现一个十阶递推式,然后就矩阵快速幂。
T3
题意
提答题。
注意其中的program.exe是可以用来测试的!
题解
接下来说每个点是什么:
- 给定一个数,求对某个数取模后的值。
- 给定一张图,求图中桥的个数
- 求凸包上的点数
- 求1到n的最短路
- 最大权匹配
- 求最长的三位偏序
- 求最小生成树的直径
- 求单调递增的子序列数目
- 求本质不同的子串数目
- 求生命游戏若干轮后的生命数目