随笔

一些想法…

关于优化建图

LOJ6036
优化建图(比如前后缀)的某个结构,只能优化例如 S 点集向 T 的连边,两个集合不能有交,T 中的点的连边不能用此结构优化。也就是有向的结构。例如本题,按照 trie 的形态分别建立向上相加的辅助点、边,但是由于 2-sat 的两个点都会向对方连边,因此不能这样建图。优化前要考虑仔细。

关于哈希与 unordered_map:

一定要写双哈希!!!unordered_map 非常慢!!!正确简单的哈希表打开方式:第一个模数选择小一点的比如 19260817,11111117,vis 数组能开下,调用比较快,能筛去一些不合法的情况,就不需要访问第二个 unordered_map 了。

关于期望:

CF850F
https://blog.csdn.net/oi_Konnyaku/article/details/83654651
事件的期望的本质是该事件发生的所有可能性中,每一种的价值乘上概率的和。有点类似条件概率(该事件发生的所有可能性中…)。对于这个题,计算一个点i向左走一步到达n的期望,要考虑“向左走一步到达n”这个事件发生的所有可能性,也就是经过0之前先到达n的可能性中,步数乘上概率之和。根据期望的线性性,条件(指能到达n)并且i到i-1到n的期望等于条件并且i到i-1的期望加上条件并且i-1到n的期望。条件并且i到i-1的期望是(i-1)/s,条件并且i-1到n的期望是f[i-1]。因此有了博客中的式子。

关于容斥:

UOJ390
想到容斥就要大胆想下去,很多时候是可以dp优化暴力容斥的。

关于一些计数题

LOJ2732
问的是权值大于某个数的段数。这类计数题通常的想法是考虑每一段的贡献在哪里计算
zroi155
只给出边数没给出形态,大概率是记边数的 DP。首先考虑一个 scc 数组如何被构造出来,对于每一个数组有唯一的构造方法,我们就可以用这种构造方法对数组进行计数。对于本题构造方法就是如果 scc 数量不减少就尽量连出一条链,这样有更多的机会来减少 scc。同时为了有更多的不减少 scc 的机会,容易证明最优策略是令链上前 k 个点构成一个大 scc。构造方法明确了:数组不减小的时候尽量连链,否则生成一个 scc。DP 的时候就可以记链上有 i 个点,连了 j 条边,前 k 个点是一个 scc 的方案数。枚举下一条边怎么连就可以转移。

关于分块

LOJ2838
很多看起来不能做的题就大胆分块!有几类常见的分块类型:
1.每次询问一个集合的。按集合大小分块。
2.若干次操作的。维护一个最近操作集合,来一个操作就加入,当集合大小大于 B 的时候重构(把操作做完,集合清空)。
3.序列上乱搞的。
4.信息量与集合大小有关的。比如子区间个数。如果要对每个子区间维护一个信息,可能可以把序列分块,每块维护块内每个自区间的信息,块之间合并。

关于 dfs 序

无向图:非树边都是返祖边
有向图:非树边由 dfn 大的指向 dfn 小的(无祖先关系)。
奇环异或奇环为偶环,偶环异或奇环为奇环,偶环异或偶环为偶环。
LOJ2881
一条边合法的充要条件是所有奇环经过它,所有偶环不经过它。树上查分一波即可。
正确性?异或生成的奇环和偶环经过讨论(???)都不会影响答案。

关于给定区间信息或区间操作的问题

这样一道题:有 n 个 bool 变量,m 个操作,每个操作需要花费一定 wi 代价,可以知道某段区间的异或值。求最小的代价能够得到整个序列。

看起来无从下手。注意到给出的是区间异或,我们可以改成两个前缀的异或。这样问题就明了多了:有 m 个方程,n+1 个未知数,每个方程有两个未知数。已知 s0=0,可以迭代地求解未知数。因此一组方程有解,当且仅当所有的变量能通过方程与 s0 连通。求最小生成树即可。

很多区间翻转的题可以看做翻转两个数,区间异或可以看做前缀异或,区间和可以看做前缀和相减。

给式子找组合意义

很多看起来奇怪不能做的式子,一定有组合(实际)意义。

f(n,m)=f(n-1,m)*k0+f(n-1,m-1)*k1 类型的递推式

这样的递推式可以 O ( 1 ) O(1) O(1) 求解某一项。方法是赋予它组合意义,f(n,m) 表示有 n 个物品,每个物品有 k0+k1 种体积,其中 k0 个 0,k1 个 1。现在每个物品选出一个体积,求有多少种选法使得物品体积和为 m。答案显然为 ( n m ) ⋅ k 0 n − m ⋅ k 1 m {n\choose m}·k_0^{n-m}·k_1^m (mn)k0nmk1m

zroi600

n 种颜色的球,每种颜色有 ai 个(有标号),一共有 m 个球,把这些球排成一排,要求每种颜色第一次出现的球标号为1,求方案数。

转化套路

· 看到 xor/or/and 首先按位考虑。
· 看到 or/and/gcd/mod,一直操作下去最多改变 log 次。
· 一些限制左端点相同,右端点不同,从左向右做,变成了减少限制,因此可选的范围扩大。例:zroi563
· 要求什么比如多少个区间出现了奇数次什么什么,可能可以给每个东西随机一个权值,这样出现偶数次的就被异或掉了。
· 给定一个操作序列,求最早何时不满足 ××,可以二分答案。
· 要求满足某些东西 和/×× 相同,可以考虑鸽巢原理。
· 要求有关路径或者点的信息,比如多少条路径满足… 多少个点满足… 最长的路径满足… 就要想到点分。
· 看到统计多少种删除方法的题目,有时候可以考虑保留什么是合法的(JXOI2017颜色)
· 我们贪心的时候喜欢“没有后顾之忧”,有的时候贪心要考虑对后面的影响,不妨试试时光倒流(NOI2017蔬菜)

关于循环卷积

我们知道,求两个个多项式 mod x^n 下的循环卷积,只要模数有 n 次单位根,我们带入 n 个单位根,得到点值之后把点值对应相乘即可。原理是 ω n j ω n k = ω ( j + k ) % n \omega_n^j\omega_n^k=\omega^{(j+k)\%n} ωnjωnk=ω(j+k)%n,发现与循环卷积后多项式 DFT 后结果恰好相同。

对于二元函数 f ( x , y ) = ∑ i = 0 n ∑ j = 0 m a i , j x i y j f(x,y)=\sum_{i=0}^n\sum_{j=0}^ma_{i,j}x^iy^j f(x,y)=i=0nj=0mai,jxiyj,我们要求它的 k 次方在模 x n y m x^ny^m xnym 意义下的循环卷积,方法是:
1.把 y 次数相同的放在一行,x 次数相同的放在一列,得到一个 n*m 的矩阵,元素为 a i , j a_{i,j} ai,j
2.每一行 DFT,把点值看做系数,对每一列 DFT。
3.每个点值快速幂。
4.每一列 IDFT,每一行 IDFT。这时 a i , j a_{i,j} ai,j 即为 x i y j x^iy^j xiyj 的系数。

构造序列进行 DP

有时候我们想,如果题目中给的序列满足**条件,我们就比较好做。可以想办法构造一个序列(比如排序什么的),来满足我们 DP 的要求。

共有 n 艘飞船参与演练,每艘飞船都有一个武力值 w_i,你需要把它们分成两队:A 队与 B 队,每队飞船数目任意。我们发现,如果两艘飞船 i 与 j 的武力值相加不小于 m 且不在同一队,那么这两艘飞船就能配合默契。请问最多能有多少对飞船配合默契,同时还需算出有多少种分队方案可以达到此效果。
n<=2000,m<=1e6

设状态表示 分完前 i 个人,A 队有 j 个人是不能转移的,因为我们不知道新加一个人进去会产生多少贡献。换句话说,如果新加入一个人的贡献好算,就可以 DP 了。

考虑构造一个序列,对于一个 i,满足任意 j<i 都有 w j + w i < m w_j+w_i<m wj+wi<m 或者 w j + w i > = m w_j+w_i>=m wj+wi>=m,贡献就可以计算。关键是如何构造这样的一个序列。

显然要把 w 从小到大排序,如果一个区间 [l,r],w_l+w_r>=m,那么 w_r 和区间中每一个数相加都大于等于 m,我们把右端点放入构造序列的前端。否则说明 w_l 和区间任何一个数相加都小于 m,把左端点放在序列的前端。

很多时候 DP 的关键就是寻找之前加入元素(对于新添加元素)的某种共性。如果没有这种共性,就需要额外记录一些信息。这道题告诉我们,人为构造这种共性是可能的。

一些组合数技巧

∑ i = 0 n ( i k ) = ( n + 1 k + 1 ) ∑ i = 0 n ( n i ) 2 = ( 2 n n ) ( x + y k ) = ∑ i = 0 k ( x i ) ( y k − i ) \sum_{i=0}^n {i\choose k}={n+1\choose k+1}\\ \sum_{i=0}^n {n\choose i}^2={2n\choose n}\\ {x+y\choose k}=\sum_{i=0}^k {x\choose i}{y\choose k-i}\\ i=0n(ki)=(k+1n+1)i=0n(in)2=(n2n)(kx+y)=i=0k(ix)(kiy)

关于卢卡斯定理的小题

一个盒子里有 n 个黑球 n 个白球,从中选出 n 个球,求有多少种选法选出偶数个白球,对 p 取模。
n ≤ 1 0 18 , p = 1000003 n\leq 10^{18},p=1000003 n1018,p=1000003

就是要求下面的式子:
∑ i = 0 n / 2 ( n 2 i ) 2 \sum_{i=0}^{n/2}{n\choose 2i}^2 i=0n/2(2in)2

想象把每个组合数用卢卡斯定理拆开,要求的大概是外面一个sigma里面是若干pi。发现每个pi里面的括号数量是一样多的,类似中国剩余定理,我们在p进制的每一位下面求答案,不同位直接乘起来即可。容易发现直接乘起来以后,相当于从每个进制下选出一对(n,m),每一种选法与我们要计算的东西是一一对应的(对应 2i 变成 p 进制的结果)。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
#define se(x) cout<<x<<'\n'
using namespace std;
const int p=1e6+3;
typedef pair <int,int> P;
ll f0=1,f1,a[100],inv[p+10];
ll C[210][210];
int read()
{
	int x=0;char c=getchar(),flag='+';
	while(!isdigit(c)) flag=c,c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return flag=='-'?-x:x;
}
ll qpow(int a,int b)
{
	ll ans=1,base=a;
	while(b)
	{
		if(b&1) ans=ans*base%p;
		base=base*base%p;
		b>>=1;
	}
	return ans;
}
void solve(int x)
{
	ll now=1,g0=0,g1=0;
	for(int i=0;i<=a[x];i++)
	{
		
		if(i&1) g1=(g1+now*now)%p;
		else g0=(g0+now*now)%p;
		now=now*inv[i+1]%p*(a[x]-i)%p;
	}
	ll s0=f0,s1=f1;
	f1=(s1*g0+s0*g1)%p;
	f0=(s1*g1+s0*g0)%p;
}
int main()
{
	int len=0;
	inv[0]=inv[1]=1;
	for(int i=2;i<=p;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
	ll n;cin>>n;
	while(n) a[++len]=n%p,n/=p;
	while(len--) solve(len+1);
	se(f0);
	return 0;
}
/*by DT_Kang*/
关于卡时

调取程序运行时间要这么写:1.0*(clock()-T)/CLOCKS_PER_SEC,单位是 s。因为 windows 和 Linux 下 clock() 的单位不同。

关于线段树合并

如果两棵线段树同时有某个叶子,那么把他们合并的时候一定会递归到这个叶子。

偏序关系

比如要统计第 k 大的贡献,有相同元素的时候,可以定义他们的一个偏序关系,使得它们变得不同,贡献由这种偏序关系下的第 k 大唯一提供。

一些构造树的题目

很多时候题目给出的信息是比较整体的,比如“形成一个联通快”,“形成一个矩形”。为了方便维护信息或者分析性质,我们常常会把条件转化成比较局部的信息(IOI2018seat)。在这类构造体中,由于叶子比较特殊,突破口往往在叶子。

例子:要求构造一个 n 个节点的树,满足 m 个限制,每个限制给出一个点集,要求这个集合的点在树上是一个联通块。 n , m ≤ 2000 n,m\leq 2000 n,m2000

我们对于每个点求出 s_i 表示 i 存在在哪些限制里,如果一个限制只有一个点那么显然可以删去。根据上面所说的,我们想要从叶子做突破:发现包含叶子的那些限制,一定包含叶子的父亲。我们可以大胆猜测一个做法:每次找到两个点 i,j 使得 s i ⊂ s j s_i\subset s_j sisj,那么 i 向 j 连边,然后删掉 i。这显然是正确的。
复杂度 O ( n 2 m / 32 ) O(n^2m/32) O(n2m/32)

关于三分

突然发现为什么三分的时候要取三等分点啊… 直接取中间的两个点不就每次可以把区间缩小到原来的一半吗(相当于求导)

欧,大概这个算法本来是为求连续函数最值的时候的精度着想,然后直接无脑推广了罢。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值