csp冲刺考试记录总结

CSP2019冲刺考试记录总结

10.10(lost 100)

得分55分

T1期望100 \(\rightarrow\) 10

T2期望100 \(\rightarrow\) 35实际算法50分

T3 10分暴力

T1是一道分情况讨论的题 当时写了170+行,还只有10分

可以把有两个-1的情况用两个inf和一个\(3^k\)解决

然后画出没有-1,一个-1的情况,可以找到6种无解的情况,4种乘2的情况,剩下的乘1

  • and得1与or得0冲突
  • and得1与xor得1冲突
  • or得0与xor得1冲突
  • and = or = xor = 1冲突
  • and=or=0与xor = 1冲突

T2算错复杂度

对于3000000(3e6)的数据,O(nlogn)是死的

考场上写了个set炸飞还挂了

用一个vector就解决了

学到一个访问负数下标的方法

bool a[N];
bool *vis = a+3500000;//用指针解决

T3写暴力还有10分long long丢了


10.12 (lost 60)

得分100整,但是期望也不高

主要是只有第一打满了还花了大部分时间

最后一题以为是dp扎进去打了结果正解是贪心

暴力分至少有60分没拿,

T1是数学组合题就是重复元素的不重复排列数

很简单

\[ans = \frac{n!}{\prod_{i=1}^n c_i!} \]

我在考场上写的递推的也是正解,用两两合并的思想,就是把ci个相同的人一个个地插空,除去重复的全排列

T2是尺取法 也就是双指针

对于选择符合要求的另一属性,同时又要求区间的最大差最小差,就可用尺取法

通常先排序,然后维护保证双指针中间的内容满足条件,此时lr中间并不是真的全部选择了

	int l=1,r=1;
	while(r <= cnt)
	{
		buk[p[r].g]++;
		if(buk[p[r].g] == c[p[r].g]) tot++;
		if(tot == n)
		{
			ans = min(ans,p[r].h - p[l].h);
			while(buk[p[l].g] > c[p[l].g])//填>是为了l++后保证维护了合法段 尺取法 
			{
				ans = min(ans,p[r].h - p[l].h);
				buk[p[l].g]--;
				l++;
			}
			ans = min(ans,p[r].h - p[l].h);
		}
		r++;
	}

10.20(215)

T1大模拟不过打了很久
T2是用背包进行最后统计
T3不会
还可以

10.21 (70)

以为这个题好难,但是下来有感觉挺简单的

T1经典的约瑟夫问题

首先可以搞出一个\(O(n)\)递推式\(f(n) = (f(n-1) + m) \% n\)

一定要根据打出来的表仔细观察,发现他是一个分块的等差序列表,我们发现在同一块中:\(i- f(i)\)是呈现出等差递减的,且\(d=m-1\),然后最后可以通过这个差值算出这一块有多少元素,可以飞快跳到这一块中某个元素或者这一块最后一个元素。而想要到下一块的话,就在这一块最后一个元素使用递推的方法即可

假如已知这一块的第一个是第\(i\)个,其值为\(f(i)\),则这一块的元素个数为\(\lfloor \frac{i-f(i)}{m-1} \rfloor = step\)

inline int f(int n,int m)//必须要自己照着打出来的表来跳 才能懂
{
	int pos = 1,ans = 1;//这个答案表是分为等差数列的,可以用这个性质去跳 
	while(pos <= n)
	{
		int nxtpos = pos+1;
		int nxtans = (ans + m) % nxtpos;
		if(!nxtans) nxtans = nxtpos;//计算下一块的起点和它对应的函数值
		int step = (nxtpos - nxtans) / (m-1);//由下标减去函数值可以得到这一块的长度 
		if(nxtpos + step >= n)//如果答案在这一块中 
		{
			int ret = nxtans + m * (n - nxtpos);
			return ret;
		}
		nxtpos += step;
		nxtans += step * m;//跳到这一块的末尾
		pos = nxtpos;
		ans = nxtans;
	}
	return ans;
}

T2是数学和式转化+数据结构维护

一般分配律(非常重要!)

\[\sum_{j\in J, k\in K}a_jb_k=\left(\sum_{j\in J}a_j\right)\left(\sum_{k\in K}b_k\right) \]

交换求和次序公式

\[\sum_{1\le i\le n}\sum_{i\le j\le n} a_{i,j} = \sum_{1\le j\le n}\sum_{1\le i\le j} a_{i,j} \]

这个题给出一堆向量,让你求区间向量对的叉乘的平方和

\[ans = \sum_{1\le i\le k\le n}(a_ib_k - a_kb_i)^2 \\ =\sum_{1\le i\le k\le n}(a_ib_k)^2 + (a_kb_i)^2 - 2a_ib_ia_kb_k\\ = \sum_{i\in [1,n]}\sum_{k\in[i,n]} (a_ib_k)^2 + \sum_{i\in [1,n]}\sum_{k\in[i,n]} (a_kb_i)^2 - 2\sum_{1\le i\le k\le n}a_ib_ia_kb_k \]

对第二项改变求和次序:

\[=\sum_{i\in [1,n]}\sum_{k\in[i,n]} (a_ib_k)^2 + \sum_{k\in [1,n]}\sum_{i\in[1,k]} (a_kb_i)^2 - 2\sum_{1\le i\le k\le n}a_ib_ia_kb_k \]

此时第二项的k与第一项的i等价,注意第一项的k和第二项的i可以合并,相当于两个三角形拼在一起形成正方形

\[=\sum_{i\in [1,n]}\left(\sum_{k\in[1,i]}(a_ib_k)^2 + \sum_{k\in[i,n]}(a_ib_k)^2\right)- 2\sum_{1\le i\le k\le n}a_ib_ia_kb_k \\ =\sum_{i\in[1.n]}\sum_{k\in[1,n]}(a_ib_k)^2 - 2\sum_{1\le i\le k\le n}a_ib_ia_kb_k \]

i==k时叉乘为0,不碍事,接下来用一般分配律

\[= \left(\sum_{i\in[1,n]}a_i^2\right)\left(\sum_{i\in[1,n]}b_i^2\right) - 2\sum_{1\le i\le k\le n}a_ib_ia_kb_k \]

漂亮!接下来我们对右边同样这么搞

\[= \left(\sum_i^na_i^2\right)\left(\sum_i^nb_i^2\right) - \left(\sum_{i\in[1,n]}\sum_{k\in [i,n]}(a_ib_i)(a_kb_k) +\sum_{i\in[1,n]}\sum_{k\in [i,n]}(a_ib_i)(a_kb_k) \right)\\ = \left(\sum_i^na_i^2\right)\left(\sum_i^nb_i^2\right) - \left(\sum_{i\in[1,n]}\sum_{k\in [i,n]}(a_ib_i)(a_kb_k) +\sum_{i\in[1,n]}\sum_{k\in [1,i]}(a_ib_i)(a_kb_k) \right)\\ = \left(\sum_i^na_i^2\right)\left(\sum_i^nb_i^2\right) - \left(\sum_{i\in[1,n]}\sum_{k\in [1,n]}(a_ib_i)(a_kb_k) \right) \\ =\left(\sum_i^na_i^2\right)\left(\sum_i^nb_i^2\right) - \left( \sum_{i\in[1,n]}a_ib_i\sum_{i\in[1,n]} a_ib_i\right) \]

噔噔咚!

\[ans =\left(\sum_i^na_i^2\right)\left(\sum_i^nb_i^2\right) - \left( \sum_i^na_ib_i\right)^2 \]

用三棵bittree,分别维护:\(a_i^2\)的2前缀和,\(b_i^2\)的前缀和,\(a_ib_i\)的前缀和

T3经典模型:LCIS最长公共上升子序列

懒得再打一遍,详细见我的手写笔记本

10.22(AK300分!)

数据出错了害我不能AK

T1数据结构题,sol给了一个\(O(nlog^2n)\)的线段树做法,但是我用ODT水过去了,值得一提的是慢慢摸还hack到自己然后de了一会bug,原题来自UESTC秋实大哥与战争

T2原题序列合并,十几分钟就A了,还打了个对拍

T3二分答案+贪心,双倍经验:序列变换

T3是个经典模型:求最小的最大把序列调成严格递增的费用

二分一个费用,把前面的都压到尽量小,然后如果这里上升了mid都不合法,那么return false

10.23(80)

今天是真的涉及我的知识盲区

事实证明:想象力是算法设计的重要一环

绝了今天他们的错贪心可以AT1而且subtask没卡掉他们,suke随便摸了一组hack,椿雄就过不了


T1是一个最大子段和线段树

模型是最大M段和

考场上打了一个\(O(n^3)\)dp,是有正确性的,但是艹他就是没给三方的分

更绝的是,直接把所有正数加起来就可以过?虽然没人这么骗分,但是垃圾数据恶心我

关于这个std的正确性我们可以手动模拟一下

15 4
8 -2 6 8 -4 -3 -6 8 14 10 0 -8 -4 10 11

取1,15

-8 2 -6 -8 4 3 6 -8 -14 -10 0 8 4 -10 -11

取5,7

-8 2 -6 -8 -4 -3 -6 -8 -14 -10 0 8 4 -10 -11

取11,13

-8 2 -6 -8 -4 -3 -6 -8 -14 -10 0 -8 -4 -10 -11

取2,2

-8 -2 -6 -8 -4 -3 -6 -8 -14 -10 0 -8 -4 -10 -11

可以看到,到最后一次取的时候,所有的正数都变成了负数,不再存在正的答案。这意味着通过加上被取反的原负值,一段答案被分成两段更优的答案,满足了最大M段和

跟我以前打过的不同,这棵线段树显然还需要支持求最大子段和的l和r值

打了一遍std感觉学到了很多

inline node operator + (const node a,const node b){
	node ret;
	if(a.lmax > a.sum + b.lmax) ret.lmax = a.lmax , ret.lmax_rpos = a.lmax_rpos;
	else ret.lmax = a.sum + b.lmax , ret.lmax_rpos = b.lmax_rpos;
	if(b.rmax > a.rmax + b.sum) ret.rmax = b.rmax , ret.rmax_lpos = b.rmax_lpos;
	else ret.rmax = a.rmax + b.sum , ret.rmax_lpos = a.rmax_lpos;
	ret.sum = a.sum + b.sum;
	
	if(a.smax > b.smax) ret.smax = a.smax , ret.ansl = a.ansl , ret.ansr = a.ansr;
	else ret.smax = b.smax , ret.ansl = b.ansl , ret.ansr = b.ansr;
	
	if(a.rmax + b.lmax > ret.smax) ret.smax = a.rmax + b.lmax , ret.ansl = a.rmax_lpos , ret.ansr = b.lmax_rpos;
	return ret;
}

首先就是上面那个重载+法,泛用性很好。

保存ansl,ansr的技术:通过维护这个区间的 lmax的lmax_rpos和rmax的rmax_lpos,在维护这个区间的答案smax时,更新对应的ansl和ansr,因为存在一种情况就是答案=a.rmax+b.lmax,所以需要上面两个值

区间取负:无论怎样,一个数的取值只有正负两种,所以我们直接建两棵线段树,然后交换两者信息打上标记

这个标记稍微有点东西

tag[rt]表示这个区间reverse了多少次,1为奇数次,0为偶数次相当于没有reverse

T2才是最水的啊!!!

把一个序列往双端队列里面放,可以放头放尾,求之后的LIS

由于LIS不存在两个值相同的,所以我们直接把他reverse一下接在前面,,再直接求LIS即可

\(O(nlogn)\)求LIS有两种:用lower_bound、或者离散化+树状数组

for(int i=1;i<=n+n;i++)
{
    if(arr[i] > low[top]) low[++top] = arr[i];
    else *lower_bound(low+1,low+1+top,arr[i]) = arr[i];//lower维护贪心
}

T3是神秘的打表

后面的三角好找,然后有的行和列,对角线也好找,就是前面的三角需要有另一个东西地推过来

总之找规律出奇迹

1 1 1 1 1
4 5 6 7 8
9 16 22 29 37
16 37 64 93 130
25 71 149 256 386

自己看

10.24(20垫底)

jiangly出的题,毒瘤要我命

T1 没想过\(-0.XXX\)+读入乱七八糟 \(100 \rightarrow 0\)

T2打了个暴力就走了20分,下来发现超级简单就是裸的topsort

T3期望dp完全不会

T1是循环小数化分数

对于一个有理小数例如\(0.857[142857]\)

它由整数,有限小数部分,无线循环部分组成

\(0.857 = \frac{857}{1000}\) \(0.000[142857] = \frac{142857}{999999000}\)

几个部分相加即可,读入一定全都用string转整数,不然会被-0搞死

T2就是topsort 求最长路,topsort自带判环

把字符转化成长度为1的边,跑26次最长路

T3化和式+数据结构维护

10.25(120 再次倒数)

这套题主要还是我太过粗心

T1打满了主要是自己谨慎地验证了结论,还打了暴力对拍,可是可以hack的都是小数据,以后这种结论题一定要验证小数据

T2是一个区间dp,但是有一个最后阶段的小问题没想完就在打,打完了打错了还觉得毫无违和感,而且过了样例卧槽

最后30分钟手动一模就hack了,然后就是痛苦地补救,直到交了之后才发现

区间合并时枚举了分成几段,然后又枚举了断点,枚举断点的作用并不是把左右k-1段的区间合并,很显然是把左边区间给乘上一段新的,然后利用了前缀和,保证了\(O(n^4)\)

最后发现自己前面读入之后没取模,后面倒是模得起劲

	for(int len=2;len<=n;len++)//做区间dp 
	{
		for(int i=1;i<=n+n;i++)
		{
			int j = i+len-1;
			if(j > n+n) continue;
			for(int k=2;k<=min(len,m);k++)//枚举分成几部分 
			{
				g[i][j][k] = 0x7f7f7f7f;
				for(int d=i;d<j;d++)//枚举断点 
				{
					if(k-1<=d-i+1) f[i][j][k] = max(f[i][j][k] , f[i][d][k-1] * f[d+1][j][1]);
					if(k-1<=d-i+1) g[i][j][k] = min(g[i][j][k] , g[i][d][k-1] * g[d+1][j][1]);
				}
			}
		}
	}

T3是一个树形dp,但是我状态设计错了,觉得空间够就设计了无用的状态,然后被自己弄混了

\(f[x][i]\)表示x点在载重为i的时候的最大亮度,不包含自己

当然这个转移就是\(O(n^2)\)的合起来仍然是题目所要求的\(O(n^3)\)

保存路径也不难,在答案更新时把路径复制过来就可以了,枚举时就是根和子节点分配的载重,然后很好写了

int f[402][202];//表示x利用了j的载重力,不包括自己 
vector <int> g[402][202],path;//利用vector保存路径 
inline void dfs(int x)
{
	for(int d=0;d<(int)G[x].size();d++)
	{
		int v = G[x][d];
		dfs(v);
		register int i=0,j=0;
		for(i=p[x];i>=0;i--)//分配根的载重 
		{
			for(j=0;i-j-w[v]>=0;j++)//分配下面v点的载重 
			{
				if(f[x][i] <= f[v][j] + l[v] + f[x][i-j-w[v]])//最后被忘了算上v的重量 
				{
					f[x][i] = f[v][j] + l[v] + f[x][i-j-w[v]];
					g[x][i] = g[x][i-j-w[v]];//直接复制路径,注意这里是两条路径的合并 
//					g[x][i].insert(g[x][i].end(),g[v][j].begin(),g[v][j].end());这么写稍微慢些 
					for(int k=0;k<(int)g[v][j].size();k++) g[x][i].push_back(g[v][j][k]);
					g[x][i].push_back(v);
				}
			}
		}
	}
}

总结一下,如果数据小一定要手摸hack自己,不管可不可以验证,万一爆了呢?

一定要大胆猜想,小心求证,也要敢于提出质疑和推翻自己,走上正确的思路后自然算法水到渠成,可是栽在这里就太过可惜

dp虽然在复杂情况下力求状态泛化表示,可是如果要求的东西太多,不如专心让他只表示答案的一部分,实在不行还有g数组呢

10.26(20分)

没想到是雅礼的题,我说这两天怎么眼皮跳呢,就跳出来个这

昨晚上做梦梦见考试时3道题难度是倒着的,结果今天T1爆难,人人都在打搜索但是我交了个dp

T1正解是一个非常厉害的的状压dp

给定一个 \(n\times m\) 的按钮矩阵,按下每个按钮可以使自己和上下左右的状态变为 1,但要花费 \(a_{i,j}\)的代价。每个按钮有一个初始 0/1 状态,问将 \(n\times m\) 的矩阵状态全部变为 1 的最小代价。

在考场上写了一个涉及两行的dp,枚举它上一行的覆盖状态和这一行的覆盖状态,一算复杂度是够的,然后 就挂了。显然单单枚举这两个东西是无法保证正确性的,因为上一行想要被覆盖就必然要考虑上上行。这样一种可以涉及三行的策略选择类dp,显然要想办法枚举三行的状态

然后题解给出一个70分的dp,如果我多写几个小时说不定能想到,

\(f[i][k][u]\)表示第i行的选择状态为u,第i-1行的选择状态为k时的保证i-1行及以前全部被覆盖满的最优解,不保证第i行是否覆盖完

观察一下这个设计,它虽然看似有后效性,但由于它的第i行不一定覆盖完,所以在枚举第i+1行时才能配合之前的状态得出合法转移

枚举第i行,第i-1行,第i-2行的选择状态,然后就可以得出第i-1行的覆盖状态,验证是否完全覆盖,然后转移即可

for(int i=2;i<=n+1;i++)
    for(int j=0;j<=maxs;j++)//第i-2行 
        for(int k=0;k<=maxs;k++)//第i-1行 
            for(int u=0;u<=maxs;u++)//第i行 
            {
                int sk = ((j|u|pic[i-1]|k)|((k<<1)|(k>>1)))&maxs;//第i-1的覆盖情况 
                if(sk==maxs)//保证上一行 填满 
                    f[i][k][u] = min(f[i][k][u], f[i-1][j][k] + cost[i][u]);
            }

注意最后的答案需要枚举\(f[n+1][s][0]\)这里是保证前n行都填满

然后是100分的优化

\(f[i][j][k]\)表示第i行选择状态为k,覆盖状态为j时的最优解

显然k是j的子集,这样就删去了大量的无效状态,

这里记住一个小技巧:枚举子集

for(int k=j;;k=(k-1)&j)//枚举j的子集(第i-1行的选择状态)
{
    //搞事情
    if(k==0) break;//0也是子集中的一个,所以要上面执行完才退出
}

这下枚举上面i-1行的覆盖状态j和选择状态k,然后枚举第i行的选择状态,就可得到第i行的覆盖状态,注意到这里在已知所有上面和这一行的选择状态是,覆盖状态是通过计算唯一得到的,那么看似枚举j和k是无序的,然而判一下inf就可以得知是否是合法状态了

最妙的一点莫过于选择状态k是由覆盖状态j枚举子集得来的

第i-1行的覆盖状态j和选择状态k是 有机结合的,用他们两个就可以在满足i-1行填满,第i行位置的转移了

在统计答案时,也要仿造转移的方式,枚举覆盖状态后保证覆盖第n行,最后枚举选择状态即可

for(int i=1;i<=n;i++) 
    for(int s=0;s<=maxs;s++)
        for(int j=1;j<=m;j++)
            cost[i][s] += val[i][j] * ((s>>(m-j)) & 1);

for(int s=0;s<=maxs;s++) 
{
    int ss = (s|(s<<1)|(s>>1))&maxs;//左移右移就得到覆盖状态
    f[1][ss][s] = cost[1][s];//置第一行的初始状态 
}

for(int i=2;i<=n;i++)
    for(int j=0;j<=maxs;j++)//枚举第i-1行通过k的选择  的覆盖状态 
        for(int k=j;;k=(k-1)&j)
        //枚举j的子集(第i-1行的选择状态),保证了舍弃无用状态,利用了合法状态 
        {
            if(f[i-1][j][k]<inf)//保证可以到达这个情况 
            {
                for(int u=0;u<=maxs;u++)
                {
                    int sup = (j|u|pic[i-1])&maxs;
                    int si = (u|(u<<1)|(u>>1)|k)&maxs;
                    if(sup==maxs)//保证第i-1行填满 
                        f[i][si][u] = min(f[i][si][u], f[i-1][j][k] + cost[i][u]);
                }
            }
            if(k==0) break;
        }
for(int s=0;s<=maxs;s++)//枚举最后一行的覆盖状态
{
    if((s|pic[n]) == maxs)
    {
        for(int u=s;;u=(u-1)&s)//枚举最后一行的选择状态
        {
        ans = min(ans,f[n][s][u]);
        if(u==0) break;
        }
    }
}
io << ans;

10.28(270)

今天这套题非常的可做

T1打表找递推式,我先摸了前10的情况,猜对了一半然后才想通暴力怎么打,顺序错了

打了暴力发现错了从32往后错掉了,改了一下就过了,exciting

这个递推式:

for(int i=2;i<=n;i++)
{
    if(i%2==0) f[i] = (f[i-1] + f[i>>1]) % mod;
    else f[i] = f[i-1];
}

证明:

首先知道奇数的答案就是对应的偶数的答案,因为相当于偶数的答案+1

\(f(n) = f(n-1) \ \ \ (n是奇数)\)

如果n为偶数,那么把他的方案分成两部分:包括1的和不包括1的

包括1的显然就是他的上一个数的所有方案每个+1,

不包括1的至少都包括2,所以显然是比它小一半的数每个方案都乘2

\(f(n) = f(n-1) + f(\lfloor\frac{n}{2} \rfloor)\)

其实最好想的还是用完全背包做,物品就是2的方幂,草这么想真的太简单了

for(int b=1;b<=n;b<<=1)
	for(int i=1;i<=n;i++)
		f[i] = (f[i] + f[i-b]) % mod;

T2一开始觉得是求最长路,然后多画了几张图,发现其实是求最短路里面的最长值。

考虑两个点的高度差,他们至多只差最短路*d的高度,其余来的更长路,由于要满足最短路,不可能使答案更大

但是我偏偏没画图不连通的情况,于是-1的情况有一大堆没判,丢了30分

T3是有一个贪心的思想,但是一想的确只能这么做,

思考把一个序列放好的最小代价,肯定是要使移动的数最少,那么剩下没有移动的一定是连续上升的,要是剩下没有移动的最大,那么就是求最长连续上升子序列

这个东西用dp求,非常地简单

for(int i=1;i<=n;i++)
{
    f[arr[i]] = f[arr[i]-1] + 1;
    ans = max(ans,f[arr[i]]);
}
io << n - ans;

今天学到的:

不到最后一刻绝不放弃hack!

10.29 - 1(210)

T1是昨天T3绝了总之秒了100分

T2是一个类似模拟,写一个暴力把所有碰撞时间按顺序执行有70分

正解是一个链表+模拟+优先队列,每下一次碰撞一定发生在两个相邻的之间,用优先队列每次取出最靠前的一个碰撞event,用链表维护相邻关系

代码上有很多可学的地方

struct frac{
	ll fz,fm;
	frac(){}
	frac(ll fz,ll fm) : fz(fz) , fm(fm){}
	friend inline bool operator < (const frac a,const frac b){
		return a.fz * b.fm < a.fm * b.fz;//比较分数的方法,交叉相乘
	}
	friend inline bool operator == (const frac a,const frac b){
		return a.fz * b.fm == a.fm * b.fz;
	}
	inline frac fix(){ll d = gcd(fz,fm);fz /= d, fm /= d;return *this;}
	inline void print(){fix();printf("%lld/%lld\n",fz,fm);}
};
struct event{
	frac time;
	int id;
	event(){}
	event(const frac &_time,const int &_id):
		time(_time) , id(_id){}
	friend bool operator < (const event a,const event b){
		if(a.time == b.time) return a.id < b.id;
		return a.time < b.time;
	}
};
set <event> s;
sort(a+1,a+1+n,cmp);//按位置排序

for(int i=1;i<=n;i++) nxt[i] = i+1 , pre[i] = i-1;
nxt[n] = 1 , pre[1] = n;
for(int i=1;i<=n;i++) s.insert(event(gettime(i,nxt[i]) , i));
event nows;
for(int i=n-1;i>=1;i--)	
{
    nows = *s.begin();
    if(i == 1) break;
    int out = nows.id;
    if(a[out].power > a[nxt[out]].power) out = nxt[out];
    s.erase(event(gettime(out,nxt[out]) , out));
    s.erase(event(gettime(pre[out],out) , pre[out]));
    nxt[pre[out]] = nxt[out];
    pre[nxt[out]] = pre[out];

    s.insert(event(gettime(pre[out],nxt[out]) , pre[out]));
}
nows.time.print();

T3是一个背包,我打了40分暴力,正解暂时没懂 占坑

10.29 -2(210)

牛客\(CSP-S\)提高组赛前集训营1,是好题

\(T1\)仓鼠的石子游戏

是一个博弈论,首先先手必败,1的个数为奇数个,则胜负逆转

\(T2\)乃爱与城市拥挤程度

换根\(dp\),我现在只写出第一问 占坑

然后看到\(jiangly\)的代码他写的\(C++14\)给我的幼小心灵造成震撼

\(update\)

看了阿符+问了一下他,我好像懂了现在

求每个点 距离他为在\(k\)以内的点的个数,以及当以这个点为根时,这些\(k\)以内的点提出来来一颗子树,然后求每个点的\(siz\)的乘积。

换根\(dp\)就是要收得狂野,下放得谨慎

点的个数是好求的,设\(f[x][i]\)表示离点\(x\)距小于等于\(i\)的点的个数

\[for(i:0\rightarrow k) \ \ \ f[x][i] = 1\\for(j:1 \rightarrow k) \ \ \ f[x][j] = \sum_{v\in son } f[v][j-1] \]

下放的时候,减去外面的值即可

然后重点还是乘积

\(g[x][i]\)表示不含根时的答案,\(G[x][i]\)表示含根时的答案

一口气放上来

inline void dfs(int x,int fa)
{
	for(int i=0;i<=k;i++) g[x][i] = f[x][i] =  1;
	Auto(i,x)
	{
		int v = e[i].v;
		if(v==fa) continue;
		dfs(v,x);
		for(int j=1;j<=k;j++) f[x][j] += f[v][j-1] , g[x][j] = g[x][j] * G[v][j-1] % mod;
	}
	for(int i=0;i<=k;i++) G[x][i] = g[x][i] * f[x][i] % mod;//含根,手动乘上
}
inline void dfs_sp(int x,int fa)
{
	if(x!=1) 
	{
		for(int i=k;i>=2;i--) //f[fa][i-1] - f[x][i-2]表示换根时外面的与x距离为i的点
			g[x][i] = g[x][i] * g[fa][i-1] %mod * ksm(G[x][i-2] , mod-2) % mod * (f[fa][i-1] - f[x][i-2]) %mod;
		for(int i=k;i>=2;i--)
			f[x][i] += f[fa][i-1] - f[x][i-2]; 
		f[x][1]++;//手动算上父亲
		for(int i=0;i<=k;i++) G[x][i] = g[x][i] * f[x][i] % mod;//依然是手动乘根
	}
	Auto(i,x)
	{
		int v = e[i].v;
		if(v==fa) continue;
		dfs_sp(v,x);
	}
}

就是这一句

g[x][i] = g[x][i] * g[fa][i-1] %mod * ksm(G[x][i-2] , mod-2) % mod * (f[fa][i-1] - f[x][i-2]) %mod;

\[g[x][i] = g[x][i] \times g[fa][i-1] \div G[x][i-2] \times (f[fa][i-1] - f[x][i-2]); \]

乘上父亲的不含根的\(i-1\)的答案,得到\(i\)的,把重复的除掉(乘逆元),此时\(fa\)\(x\)\(siz\)一定是变了的,而\(f[fa][i-1] - f[x][i-2]\)这个式子虽然在转移\(f\)时用到,但在这里就是\(fa\) 的新的唯一的贡献

换根法的特点

改变的dp信息其实非常少,往往只影响到x,y两个节点。

用这么一棵树来研究,有奇效

\(T3\)小w的魔术扑克

只打了暴力有30,可是把数据放过去居然可以达到70我傻了,

正解是并茶几

k张正反面的卡片,打出时只能选择一面,查询q次,每次问是否能组成l到r的顺子

10.30(40)

又炸了 ,依旧还是难一点的题拿不到分

今天T1暴力本来大家都打的60分,我打了30分就走了,心想60分跑不出来,没想到直接把第三层推出来,然后新加入的点一起循环起走就可以

正解是并查集,格点上的行列问题可用并查集处理

思考一个矩阵由两个行两个列组成,我们让四个点之间有联系,那么就是把这些行和列当做并查集的点连起来,一个点在被选中,当且仅当其余三点被选中,那么其余三点已经将行和列全部并在一起,而新的点只需要判断行和列是否在同一并查集即可,

为什么有传递性?观察发现,通过一个行连在一起的两个列上,只要有一个新的行插入,那么产生的在两个列上的交点,必然会属于一个矩形,同理两个行与新插入的列也一样。这个其实不叫传递性,是广义上的性质合并

一个技巧二维前缀和,这个题里面是静态矩阵查询,所以用前缀和\(O(1)\)解决

sum[x][y] = 1;
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
        sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
    }
}
for (int i = 1, x1, y1, x2, y2; i <= q; i++) {
    io >> x1 >> y1 >> x2 >> y2;
    io << sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1] << '\n';
}

T2是一个期望dp+kmp

posted @ 2019-10-26 19:12  tythen  阅读(94)  评论(0编辑  收藏  举报