• csp冲刺考试记录总结


    CSP2019冲刺考试记录总结

    10.10(lost 100)

    得分55分

    T1期望100 ( ightarrow) 10

    T2期望100 ( ightarrow) 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} floor = 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_{jin J, kin K}a_jb_k=left(sum_{jin J}a_j ight)left(sum_{kin K}b_k ight) ]

    交换求和次序公式

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

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

    [ans = sum_{1le ile kle n}(a_ib_k - a_kb_i)^2 \ =sum_{1le ile kle n}(a_ib_k)^2 + (a_kb_i)^2 - 2a_ib_ia_kb_k\ = sum_{iin [1,n]}sum_{kin[i,n]} (a_ib_k)^2 + sum_{iin [1,n]}sum_{kin[i,n]} (a_kb_i)^2 - 2sum_{1le ile kle n}a_ib_ia_kb_k ]

    对第二项改变求和次序:

    [=sum_{iin [1,n]}sum_{kin[i,n]} (a_ib_k)^2 + sum_{kin [1,n]}sum_{iin[1,k]} (a_kb_i)^2 - 2sum_{1le ile kle n}a_ib_ia_kb_k ]

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

    [=sum_{iin [1,n]}left(sum_{kin[1,i]}(a_ib_k)^2 + sum_{kin[i,n]}(a_ib_k)^2 ight)- 2sum_{1le ile kle n}a_ib_ia_kb_k \ =sum_{iin[1.n]}sum_{kin[1,n]}(a_ib_k)^2 - 2sum_{1le ile kle n}a_ib_ia_kb_k ]

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

    [= left(sum_{iin[1,n]}a_i^2 ight)left(sum_{iin[1,n]}b_i^2 ight) - 2sum_{1le ile kle n}a_ib_ia_kb_k ]

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

    [= left(sum_i^na_i^2 ight)left(sum_i^nb_i^2 ight) - left(sum_{iin[1,n]}sum_{kin [i,n]}(a_ib_i)(a_kb_k) +sum_{iin[1,n]}sum_{kin [i,n]}(a_ib_i)(a_kb_k) ight)\ = left(sum_i^na_i^2 ight)left(sum_i^nb_i^2 ight) - left(sum_{iin[1,n]}sum_{kin [i,n]}(a_ib_i)(a_kb_k) +sum_{iin[1,n]}sum_{kin [1,i]}(a_ib_i)(a_kb_k) ight)\ = left(sum_i^na_i^2 ight)left(sum_i^nb_i^2 ight) - left(sum_{iin[1,n]}sum_{kin [1,n]}(a_ib_i)(a_kb_k) ight) \ =left(sum_i^na_i^2 ight)left(sum_i^nb_i^2 ight) - left( sum_{iin[1,n]}a_ib_isum_{iin[1,n]} a_ib_i ight) ]

    噔噔咚!

    [ans =left(sum_i^na_i^2 ight)left(sum_i^nb_i^2 ight) - left( sum_i^na_ib_i ight)^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 ightarrow 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 imes m) 的按钮矩阵,按下每个按钮可以使自己和上下左右的状态变为 1,但要花费 (a_{i,j})的代价。每个按钮有一个初始 0/1 状态,问将 (n imes 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(lfloorfrac{n}{2} floor))

    其实最好想的还是用完全背包做,物品就是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
    ",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 ightarrow k) f[x][i] = 1\for(j:1 ightarrow k) f[x][j] = sum_{vin 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] imes g[fa][i-1] div G[x][i-2] imes (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] << '
    ';
    }
    

    T2是一个期望dp+kmp

  • 相关阅读:
    干货分享|安全测试起航之旅
    干货|宏巍软件之Java线程监控之旅
    大数据时代,业务运维驱动下的企业变革
    什么是业务运维,企业如何实现互联网+业务与IT的融合
    不谈业务运维的IT主管早晚被淘汰 这里是10条干货
    详解APM数据采样与端到端
    CTO对话:云端融合下的移动技术创新
    【1111元天天拿!充一送二玩真哒!】双十一任性活动
    引领手机流量营销 嘿嘿流量打造多场景专业服务
    “烧钱补贴”下的O2O该何去何从?
  • 原文地址:https://www.cnblogs.com/tythen/p/11745161.html
Copyright © 2020-2023  润新知