模拟赛总结

//https://images.cnblogs.com/cnblogs_com/blogs/769737/galleries/2232265/o_221024235906_12.jpg
//https://images.cnblogs.com/cnblogs_com/blogs/769737/galleries/2232265/o_221024123148_10.jpg

2024.2.6

T1 珠子

小 F 有 n颗珠子排成一个序列,每个珠子有一个颜色,颜色共有 m种,编号为 1,2,,m。她想取出一段连续的珠子,对于每一种颜色 i ,要求取出的珠子个数在[li,ri],0lirin之间。求有多少种取珠子的方案。

暴力:前缀和处理前i位第j类珠子的总数,用n2枚举l,r

期望:60pts

没用前缀和:30pts

正解:双指针

先固定左端点,然后分别去找满足颜色要求的最小r1(一般是满足下限)和最大r2(一般是达到上限),由于r1r2各颜色数目显然单调不减,所以一对(r1,r2)对答案的贡献就是r2r1+1

至于怎么较快的找到r1,r2不会先咕咕

T2 数组

初始ai=i,现有两种操作

  • A:已知p,q,修改所有数,ai=p×i+q

  • B:已知x,y,将ax改为y

给出若干操作,在每次操作后输出数组元素和

第一个好办,看第二种

第二种的麻烦点在于上一次x位置的B操作可能会被A覆盖掉,所以要记录上一次A操作的时候num,以及x处的上一次B操作的时候tag

如果tagnum,说明上一次的B操作已经被覆盖掉了,那么就减去上次A操作改的值,加上y

否则减去上次B操作改成的值y,加上y

for(int i = 1;i <= m;i++)
{
	cin >> op;
	if(op == 'A')
	{
		cin >> t[i].p >> t[i].q;
		cnt = i;//记录最新A操作的时候
		sumq = 0;//覆盖掉
		cout << (ll)t[i].p * sum + t[i].q * n << '\n';
	}
	if(op == 'B')
	{
		cin >> t[i].p >> t[i].q;
		if(tag[t[i].p] <= cnt)//上次该处的B已被覆盖
		{
			sumq = sumq + (ll)t[i].q - (ll)(t[cnt].p * t[i].p + t[cnt].q);// 减去A操作留下的值(p*i+q),加上y
			tag[t[i].p] = i;
		}
		else
		{
			int y = tag[t[i].p];
			sumq = sumq + t[i].q - t[y].q;//减去上次B操作留下的值
			tag[t[i].p] = i;
		}
		cout << (ll)t[cnt].p * sum + n * t[cnt].q + sumq << '\n';
	}
}

蒟蒻没有考虑到A操作会覆盖B,只得了一半分,gg

T3 幸运区间

已知{ai},定义幸运区间为区间内所有数的gcd=1,求幸运区间数

暴力:分解质因数,拿质因子搞

20pts(主要是数组开不了那么多)

正解:双指针(byd)

考虑一个显然的结论:若一个序列存在一个子序列,其所有数的gcd=1,那么这整个序列的数的gcd=1

那么,每次固定左端点,找到最小的r,满足[l,r]中元素gcd=1,那么根据结论:[l,r],[l,r+1],[l,r+2][l,n]均为幸运区间,也就是说一个r对答案的贡献ans+=nr+1

至于找到最小的r,可以使用一些数据结构来维护

还有一点:有结论我们还可推得:一个序列的gcd=1,这个序列的子序列的gcd一定1,所以找到r后,我们可以固定r,跳l,这样就可以省时间了

int l=1,r=1;
for(l=1;l<=n;l++)
{
	r=max(l,r);//双指针思想,性质保证了可以固定r跳l,这样就是O(n)
	while(l<=r&&r<=n)
	{
		if(st.getgcd(l,r,1,n,1)==1) break;//此处使用线段树,由结论可知gcd有传递性
		r++;
	}
	ans+=n-r+1;
}

T4 找不同

已知一串单词,给定若干区间,判断每个区间中是否有重复单词

先Hash,此处使用map开的mp和last

类比HH的项链,不同单词即不同颜色,那么没有重复就是区间内颜色种类等于区间长度

蒟蒻没想到类比,打的暴力,又gg了,只能说树状数组那章是划水过来的

for(int i = 1;i <= q;i++)
{
	int l1 = b[i].l;
	int r1 = b[i].r;
	while(pos <= r1)
	{
		add(pos,1);
		if(last[mp[pos]] == 0) last[mp[pos]] = pos;//记录上一次出现位置
		else
		{
			if(last[mp[pos]] < pos)//上一次出现位置在区间内
			{
				add(last[mp[pos]],-1);//把原来加的1减掉
				last[mp[pos]] = pos;//更新
			}
		 } 
		 pos++;
	}
	ans[b[i].id].val = sum(r1) - sum(l1 - 1);
	ans[b[i].id].l = l1;
	ans[b[i].id].r = r1;
}

两道双指针题给蒟蒻的第一感觉就是:l,r只会往一个方向走,不会反向跳到某一位置

2024.2.18

T1 家庭作业

click

设给的数为{an},{bm}

对于(ai,bj),它对答案的贡献就是gcd(ai,bj)

为了防止枚举时重复计算答案,每求完一次gcd,就要把求的gcd除掉

for(int i = 1;i <= n;i++)
{
	for(int j = 1;j <= m;j++)
	{
		int k = __gcd(a[i],b[j]);
		if(k != 1)
		{
			ans = ans * k % mod;
			a[i] /= k;
			b[j] /= k;//除掉,防止重复
		}
	}
}

硬拿gcd50pts,会T掉

本来想到O(nm)枚举了但没想到怎么避免重复,以为是假的

还是太蒟蒻了

T2 距离之和

click

md调了半天发现是指令数组只开了1e5

对于一次移动(以向右为例),向右一步,首先不影响|Δy|,那么在出发点左边的控制点到机器人的距离均+1,在出发点右边的控制点到机器人的距离均1,变化量就是两类点的数量差

其他同理

开两个权值线段树(横、纵坐标)统计点个数可过,但是考场上内存炸了直接gg

就挺烦人

其他:

我们可以把坐标排序,用二分找出发点所在分界,这样就免去了桶

//向右
int num = lower_bound(x + 1,x + n + 1,dx + 1) - x;
num = n - num + 1;//lower_bound返回的是第一个不小于dx + 1的数的位置,具体个数要处理一下
int res = n - num;
sum += res - num;//res是左边的点,距离+1,num是右边的点,距离-1
dx++;//更新坐标

或者每次以机器人位置为原点建系,把点划分成在x轴上/下、在y轴上/下,在x/y轴上六个部分以及各行各列的点数

仍以向右为例,此时上下部分不影响,在左边和在y轴上的点距离+1,在右边的点距离-1,然后更新涉及部分的点的数量

//向右
dx++;
sum += ony + zuo - you;//更新
zuo += ony;//出发点所在列上的点在新Y轴的左边
ony = px[dx];//新y轴上的点
you -= ony;//右边在dy列的点到了坐标轴上,不属于右边

T3 country

click

哈希不用说

(但正解是kmp)

按样例顺序遍历处理有10pts

听说暴力展开大写字符有30pts

乱序数据的话要记忆化搜索

坑点在于两个大写代表的串拼起来后可能会生成目标串

首先处理出模式串的fail指针

fi,j表示当前匹配到i号串,匹配指针在j处的方案数

gi,j表示把第i号串匹配完时模式串指针的位置

匹配完i串后可以直接从gi,j开始往后来实现拼接大写字母

dfs:

void dfs(int x,int pos)
{
	cout << 114514 << endl;
	if(vis[x][pos]) return;
	int j = pos;
	for(int i = 1;i <= len[x];i++)
	{
		if(a[x][i] >= 'A' && a[x][i] <= 'Z')
		{
			int v = a[x][i] - 'A';
			dfs(v,j);//指针指到了大写字母,直接连着向后匹配 
			f[x][pos] = (f[x][pos] + f[v][j]) % mod;//大写部分的匹配方案数就是f[v][j],dfs后更新答案 
			j = g[v][j];//dfs完了,说明大写字母内部匹配完毕,更新指针 
		}
		else
		{
			while(j && s[j + 1] != a[x][i]) j = fail[j];
			if(s[j + 1] == a[x][i]) j++;
			if(j == m) f[x][pos] = (f[x][pos] + 1) % mod,j = fail[j];
		}//小写字母部分,KMP版子 
	}
	vis[x][pos] = 1;
	g[x][pos] = j;//标记并记录匹配完毕的位置 
}

预处理fail:

for(int i = 2,j = 0;i <= m;i++)
{
	while(j && s[i] != s[j + 1]) j = fail[j];
	if(s[i] == s[j + 1])j++;
	fail[i] = j;
}//KMP板子预处理fail指针 

T4 太空飞船

click

byd考场上直接想这题想歪了还忘了打暴力保底

暴力n440pts

还有一个60pts的dp,平方级别的,但我写不出来,先看看正解:

容斥原理

采用 “总数-非法的方案” (容斥是不是都爱这么玩)

如果gcd1,说明四个数一定都是某个数的倍数

numi表示i的倍数的个数

首先,22,23的倍数一定是2的倍数,所以num4,num8等都包含在num2中,所以我们只需要用所含质数的次数为的i来搞答案

接下来就是容斥:减掉2,3,5的倍数,啊多减了,补上2×3,2×5,啊又加多了,减去2×3×5

ans=Cn4Cnumi4×(1)cnt

cnt就是i中有多少个质数

还有一点:从质数去枚举不方便,不妨枚举110000

for(int i = 1;i <= n;i++)
{
	for(int j = 2;j * j <= a[i];j++)
	{
		if(a[i] % j == 0)
		{
			num[j]++;
			if(j * j != a[i]) num[a[i] / j]++;
		}
	}
}//处理num数组
ans = C[n];//总数
for(int i = 1;i < N;i++)
{
	bool f = 0;
	for(int j = 2;j * j <= i;j++)
	{
		if(i % j == 0 && i / j % j == 0) 
		{
			f = 1;
			break;
		}//如果数的次数超过1
	}
	if(f) continue;
	int cnt = 0;
	int x = i;
	for(int j = 2;j * j <= x;j++)
	{
		if(x % j == 0)
		{
			cnt++;
			while(x % j == 0) x /= j;
		}//统计质数个数
	}
	if(x != 1) cnt++;
	if(cnt % 2 == 1) ans -= C[num[i]];
	else ans += C[num[i]];//容斥
}

2024.2.19

T1 素数

click

md内存又开炸了100>0啊啊啊啊不对呀我记得开的没那么扯淡呀

欧拉筛出范围内所有素数,拿前缀和O(cnt2)搞出各种连续素数和,统计表示数

for(int i = 1;i <= cnt;i++) sum[i] = sum[i - 1] + pri[i];
//cout << sum[cnt] << endl;
for(int i = 1;i <= cnt;i++)
	for(int j = 1;j <= i;j++)
	{
		if(sum[i] - sum[j - 1] > 33000) continue;
		num[sum[i] - sum[j - 1]]++;
	}

我tm考场上怎么开了5e7wc

T2 晨练

click

dp

dp(i,j)表示第i分钟,疲劳度为j是跑的最大距离

如果选择跑,那就是

dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+d[i+1]);

如果不跑,那么直接休息

坑点:疲劳度为0后还能继续休息

dp[i+j][0]=max(dp[i+j][0],dp[i][j])

dp[i+j+1][0]=max(dp[i+j+1][0],dp[i+j][0])(从上一分钟的休息状态转移)

初始化:

dp[1][1]=d[1];dp[1][0]=0

ok,考场上还好从坑里爬出来了

100pts

T3 奇怪的桌子

click

20pts:Cn×mk

还有十分的小数据,手算的,好像算错了

找规律

考场上想到了一点


(考试时画的)

有一些列的点数是有规律的(有两个点的是1,4,7,有一个点的是3,6

再看一个一列放三个点的

列也有规律,似乎是i%n相同

那我们就猜测:

i%n相同的列所含点数相同

证明:

这一点的用处就在于:若某一列有j个点,那么贡献就是(Cnj)mn

然后dp (啊?)

dp(i,j)表示到第i列,已经放了j个点

如果我们在当前列要放k个点,就可以根据规律可得

dp(i,j)+=dp(i1,jk)×(Cnk)mn+(i<=m%n)

说明:组合数的指数部分,结合一列三个点的图可以得到。即如果i<=m%n,就又包含了一列

但是有一些疑点:

一共m列,而m1018

而且上面两幅图的总点数也是不一样的

i,j的范围又是多少?

我们观察到对于一种方案,每个正方形内部点的分布是一致的,比如第一张图中,正方形内都是一列1个,一列2个,一列没有。第二张图中,正方形内都是仅一列有3个点

那么我们是不是可以用一个正方形内部的点分布来代替填满整个图,即一种点分布就是一种方案

可以,结合规律,我们就可以用正方形内部的列把其他列推出来

所以只需要dp一个正方形,答案就是dp(n,k)

for(int i = 1;i <= n;i++)
	for(int j = k - (n - i) * n;j <= k;j++)//这里j的下线意思是:还剩n-i列未填,这里面最多填(n-i)*n个点,那么前面至少填了k-(n-i)*n个点
		for(int l = 0;l <= min(n,j);l++)
			dp[i][j] = (dp[i][j] + dp[i - 1][j - l] * qpow(C(n,l),(m / n + (i <= m % n)) % (mod - 1)))% mod;

还没完

kN2。循环是个O(N2k),还有一个快速幂的log,极限复杂度可达O(n4log),虽然实际上没有这么多,但由于极限过大,实际中还是会T掉几个点

循环的三次方肯定砍不掉了,那就砍掉log,预处理快速幂

for(int i = 1;i <= n;i++)
	for(int j = 0;j <= max(n,k);j++)
		qp[i][j] = qpow(C(n,j),(m / n + (i <= m % n)) % (mod - 1)) % mod;

T4 学校

click

图的算法忘完了c

HXY算的时间就是最短路耗时

即求断掉某条边后是否还有和原先耗时相同的最短路

每次删边后跑dijkstra能拿30pts

由于只考虑最短路,所以可以吧原图中所有最短路(可能不止一条)经过的边提出来建新图,如果在新图中删掉某条边后图不联通,那么删掉这条边就是找不到最短路的

就是Tarjan中的桥

坑点:有重边

2024.2.21

T1 排序

click

TMD又没开long long

TMD又没开long long

TMD又没开long long

1000啊啊啊啊啊啊

正确性(排序不等式):

abcd

ab+cdac+bdad+bc

一张图解法(以n=3为例,实际上n为多少都一样)

T2 牛吃草

click

想到了二分

二分size

用类似贪心的方法(肯定是错的)尝试挂分,没想到挂出70pts

正解还用到了dp,还是经验不够没想到

fi表示考虑完[1,i]后得到的最大覆盖长度

fi=maxiwijisize(fj+(ij),fi1)

这个暴力式可得75pts

但也有坑点:max函数可能不执行,所以要提前赋上不选情况的值

for(int i = x;i <= n;i++)
{
	dp[i] = dp[i - 1];//跳出坑点
	for(int j = i - w[i];j <= i - x;j++)
	dp[i] = max(dp[i],dp[j] + (i - j));
}

接下来观察到

wi1wi1

说明

(i1)wi1(i1)(wi1)=iwi

下限单调不降,相当于滑动窗口[iwi,ix],考虑单调队列优化

for(int i = x;i <= n;i++)
{
	//if(i <= w[i]) continue;
	if(w[i] < x)//区间不存在,直接赋值跑路
	{
		dp[i] = dp[i - 1]; 
		continue;
	}
	while(h <= t && dp[i - x] - i + x >= dp[q[t]] - q[t]) t--;
	q[++t] = i - x;
	while(h <= t && i - w[i] > q[h]) h++;
	dp[i] = max(dp[i - 1],dp[q[h]] + (i - q[h]));
}

T3 树上的宝藏

click

想到的是n2的树形dp,但是处理特殊边时出了问题

dp(u,0/1)表示不选/选u时的方案数

具体的,如果一个节点u与所有儿子的连接中有特殊边,会比较麻烦

但是直接dfs无法一次得知所有儿子,就可能得到错误的值

这一部分分的题解处理方法很巧妙:断掉特殊边,对形成的两棵子树分别dp

这样就避免了特殊边的干扰,非常容易的得到dp方程

dp(u,0)=v(dp(v,0)+dp(v,1)) :不取u,子节点爱取不取

dp(u,1)=vdp(v,0):取了u,由于一定没有特殊边,所以所有子节点都不能取

对于特殊边连接的点a,b,这俩是至少选一个,答案就是

dp(a,0)×dp(b,1)+dp(a,1)×dp(b,1)+dp(a,1)×dp(b,0)

初始化:叶子结点不论状态值均为1

for(int j = 1;j <= n;j++) if(in[j] == 1) dp[j][1] = dp[j][0] = 1;
...
void dfs(int x,int fa)
{
	ll ans = 1;
	ll res = 1;
	for(int i = head[x];i;i = e[i].next)
	{
		if(e[i].idx == cut) continue;//模拟断边操作
		int k = e[i].to;
		if(k != fa)
		{
			dfs(k,x);
			ans = (ans * (dp[k][0] + dp[k][1])) % mod;
			res = (res * (dp[k][0])) % mod;//求乘积
		}
	}
	dp[x][0] = ans;
	dp[x][1] = res;
}

O(n2)狂炫60pts

正解太玄乎

还是先忽略特殊边

g(u,0/1)表示不选/选uu子树外部的答案

其实就是选定一条边后需要用到连接点外部的信息

设节点u的父亲为fa,一个子节点为v

我们把u子树外部分成两部分:fa的外面和fa内除了u的部分

父亲节点的外部就是g(fa),抛掉内部子树比较麻烦,以v节点为例

g(v,1)=g(u,0)×f(u,0)f(v,0)+f(v,1)

抽象?来看图(f就是暴力n2中的dp

由于不考虑特殊边,所以这里的u是不选的,图中红框内两部分的综合用的是乘法原理,记得结合f的求法看,由于f就是乘积,所以用除法

类似的,可以得到

g(v,0)=g(u,0)×f(u,0)f(v,0)+f(v,1)+g(u,1)×f(u,1)f(v,0)

这样求得g数组后,考虑O(1)的答案

设特殊边连接的是u,v(这里u必须是v的父亲否则会出锅),则

ansu,v=(f(v,1)+f(v,0))(上图中的橙色方框)×f(u,1)f(v,0)(上图中的绿色方框)

×g(u,1)(上图中的右上角蓝色方框)

+f(v,1)×f(u,0)f(v,1)+f(v,0)×g(u,0)(同理不赘述)

O(n),搞就完了

记得逆元+膜

T4 MEX

click

大毒瘤,暴力一点分没有

假设当前求的MEX为k,条件显然

  • 0k1都要出现
  • k不出现

枚举右端点,可知区间的左端点也在一个范围内

那么,我们就用0k1的位置限制该范围的下界,用k出现的位置限制该范围的上界。

形式化的就是(pii上一次出现的位置)

max(mini=0k1pipk,0)(求的是合法左端点的个数 )

然后再把这个公式改一下

mini=0k1pimini=0kpi

对每一个k,只需求出

mini=0kpi

然后作差就能得到答案

另一种理解:

把问题转化成求MEXk,最后作差得到答案

某区间的MEXk,说明这段区间内存在一个[0,k]未出现

固定左端点,当右端点递增时,该区间的MEX显然单调,说明合法的右端点能组成一个集合,这个集合中的最大值就是maxi=0knextl,i1(next表示从l开始第一个i出现的位置)

使用线段树维护maxi=0knextl,i1

由于l增加只会使得nextl,i增加,则对应的最大右端点也只会增加。而l右移一位只会使得一个nextl,k改变,那么就相当于对[k,n]max

然后就不会了

2024.2.22

T1 打赌

click

找规律

每四列一个周期,分成两部分,右边是不足四列的部分,左边的答案就是(c/4)×r×14

接下来摇一摇色子就可得到:

算就行了

100pts

T2 舞会

click

方法一:权值线段树维护个数,匹配上一个后删点

删点打标记很麻烦,写炸了,only20pts

还有写平衡树的

正解:先排序保证单调,然后用类似双指针的方法,对于第i个男生,找第一个符合条件的女生j,找到了就i++,j++,ans++,否则j一直右移

int l = 1;
int r = n;
while(l <= n && r >= 1 && b[l] < 0 && g[r] > 0)
{
	if(-b[l] > g[r])
	{
		ans++;
		l++;
		r--;
	}
	else r--;
}//男比女高
l = 1,r = n;
while(l <= n && r >= 1 && g[l] < 0 && b[r] > 0)
{
	if(-g[l] > b[r])
	{
		ans++;
		l++;
		r--;
	}
	else r--;
}//女比男高

T3 最小生成树

click

首先,由于一定存在一棵最小生成树,是1n从小到大连成一条链,每条链的边权为1,而最小生成树边数一定,所以有结论

只有互质的两数之间的边才有可能被保留,否则生成树一定不是最小

想到这里了,然后就想着用组合and容斥原排除非法情况,没弄出来,废了

看到互质,还可以想到欧拉函数

注意到题目要求父节点标号小于子节点,而φ(n)统计的与n互质的数均小于n,那么就相当于节点n的父亲有φ(n)

利用乘法原理得到

ans=i=1nφ(i)

T4 买汽水

click

状压暴力有30pts

正解:折半搜索

直接搜复杂度是240会爆掉,剪枝优化能提到60pts左右,折半搜索就是分成两部分去搜,复杂度降到2×220,又因为左右两部分互不影响,所以可以排序后用指针合并答案

void dfs1(int x,int sum)
{
	if(x == n / 2 + 1) ans1[++tot] = sum;
	else 
		for(int i = 0;i <= 1;i++)
			dfs1(x + 1,sum + (i?a[x]:0));
}//dfs1(1,0)左半部分
void dfs2(int x,int sum)
{
	if(x == n + 1) ans2[++num] = sum;
	else 
		for(int i = 0;i <= 1;i++)
			dfs2(x + 1,sum + (i?a[x]:0));
}//dfs2(n / 2 + 1,0)右半部分
int l = 1;
	int r = num;
	ll ans = -1;
	while(l <= tot && r)
	{
		while(ans1[l] + ans2[r] > m) r--;//找到第一个符合条件的r
		if(r) ans = max(ans,ans1[l] + ans2[r]);
		l++;//排序后这样做只会使和增加,所以不用动右指针
	}

2024.3.10

T1 [USACO09MAR] Cow Frisbee Team S

click

01背包求方案数

十来分钟打了个板子小代码就溜了

结果不出意外的出意外了——T了,40pts

int n,f;
int a[N];
map<ll,int> dp;
int sum = 0;
int main()
{
	scanf("%d%d",&n,&f);
	for(int i = 1;i <= n;i++) scanf("%d",&a[i]),sum += a[i]; 
	dp[0] = 1;
	for(int i = 1;i <= n;i++)
	{
		for(int j = sum;j >= a[i];j--)
		{
			dp[j] = (dp[j] + dp[j - a[i]]) % mod;
		}
	}
	ll ans = 0;
	for(int i = f;i <= sum;i += f)
		ans = (ans + dp[i]) % mod;
	printf("%d",ans);
	return 0;
}

后来经wzw奆神点拨才发现:

所以他要求队伍的总能力必须是
F的倍数

这不是排列的trick吗?

所以把上面代码滚掉的一维(第几个)拿回来,开第二维表示余数,余数那一维走背包操作,答案就是dpn,0

for(int i = 1;i <= n;i++) scanf("%d",&a[i]),dp[i][a[i] % f] = 1;//初始化
for(int i = 1;i <= n;i++)
{
	for(int j = 0;j < f;j++)
	{
		dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;
		dp[i][j] = (dp[i][j] + dp[i - 1][((j - a[i]) % f + f) % f]) % mod;//内部取余操作是为了防止出现负数
	}
}
printf("%d",dp[n][0]);

T2 Prufer 序列多源生成树

click

标题党

还以为是Prufer,当场吓傻了,10秒走人:

printf("1");

wc60pts

正解是什么树的直径,用搜索就行

题意要求在保证深度最小的情况下最小化根的编号

保证树的深度最小,可以计算树的直径并取得最小,在保证直径最小下令编号最小,用树上DFS/BFS,也可以用树形dp

//树形dp求直径
//dp[i]:以i为根的子树中最长路径长度 
//g[i]:以i为根的子树中次长路径的长度
//id[i]:i对应的最长路径端点 
void dfs1(int x,int fa)
{
	for(int i = head[x];i;i = e[i].next)
	{
		int k = e[i].to;
		if(k == fa) continue;
		dfs1(k,x);
		if(dp[x] < dp[k] + 1)
		{
			g[x] = dp[x];
			dp[x] = dp[k] + 1;
			id[x] = k;
		}
		else if(g[x] < dp[k] + 1) g[x] = dp[k] + 1;
	}
}
//合并找直径 
void dfs2(int x,int fa)
{
	d[x] = d[fa] + 1;
	if(x == id[fa]) d[x] = max(d[x],g[fa] + 1);
	else d[x] = max(d[x],dp[fa] + 1);
	for(int i = head[x];i;i = e[i].next)
	{
		int k = e[i].to;
		if(k != fa) dfs2(k,x);
	}
	dp[x] = max(dp[x],d[x]);
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n - 1;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u); 
	}
	//printf("1");
	dfs1(1,0);
	dfs2(1,0);
	int ans = 1;
	for(int i = 1;i <= n;i++)//从小号开始枚举
		if(dp[ans] > dp[i]) ans = i;
	printf("%d",ans); 

T3 P3216 [HNOI2011] 数学作业

click

30pts很好来:暴力O(n)

ll check(ll x)
{
	if(1 <= x && x <= 9) return 10;
	if(10 <= x && x <= 99) return 100;
	if(100 <= x && x <= 999) return 1000;
	if(1000 <= x && x <= 9999) return 10000;
	if(10000 <= x && x <= 99999) return 100000;
	if(100000 <= x && x <= 999999) return 1000000;
	if(1000000 <= x && x <= 9999999) return 10000000;
}
int main()
{
	ll n,mod;
	scanf("%lld%lld",&n,&mod);
	ll ans = 0;
	for(int i = 1;i <= n;i++)
	{
		ans = (ll)(ans * check(i) % mod + i % mod) % mod;//递推
	}
	printf("%lld",ans);
	return 0;
}

上面的代码很暴力,但是有一点值得肯定:

找到了递推式,也就是dp

dpi=(dpi1×10num+i)%mod

numi

n1018,肯定需要log级别的加速方法

还是优化dp,指向很明显了

矩阵快速幂

先用dp式构出矩阵雏形

[dpi]=[10num1]×[dpi1i]

完善

[dpidpi+1]=[10num10]×[dpi1i]

[dpii+11]=[10num10011001]×[dpi1i1]

递归

[dpnn+11]=[10num10011001]n×[dp011]

这里num会随着数的位数变化,所以要对num相同的区间做快速幂,然后跨区间时变一下num

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,mod;
ll ten[20];
struct ma
{
	ll m[5][5];
}ans,base;
ma cal(ma a,ma b)
{
	ma tmp;
	for(int i = 1;i <= 3;i++)
	{
		for(int j = 1;j <= 3;j++)
		{
			tmp.m[i][j] = 0;
			for(int k = 1;k <= 3;k++) tmp.m[i][j] = (tmp.m[i][j] + a.m[i][k] % mod * b.m[k][j] % mod) % mod,tmp.m[i][j] = (tmp.m[i][j] % mod + mod) % mod;
			//cout << tmp.m[1][1] << endl;
		}
	}
	return tmp;
}
void init(int num)
{
	ans.m[1][1] = ten[num] % mod;//记得取模
	ans.m[1][2] = ans.m[2][2] = ans.m[2][3] = ans.m[3][3] = 1;
	ans.m[1][3] = ans.m[2][1] = ans.m[3][1] = ans.m[3][2] = 0;
	/*for(int i = 1;i <= 3;i++)
	{
		for(int j = 1;j <= 3;j++)
			cout << ans.m[i][j] << " ";
		cout << endl;
	}*/
}//预处理左上角10的num次方
ma qpow(ll num,ll ci)
{
	init(num);
	base = ans;
	while(ci)
	{
		if(ci & 1) ans = cal(ans,base);
		base = cal(base,base);
		ci >>= 1;
	}
	return ans;
}
int main()
{
	scanf("%lld%lld",&n,&mod);
	ten[0] = 1;
	for(int i = 1;i <= 18;i++) ten[i] = ten[i - 1] * 10;//10的若干次方
	int cnt = 0;
	ll x = n;
	while(x)
	{
		x /= 10;
		cnt++;
	}/最大位数
	//cout << cnt << endl;
	ma A;
	A.m[1][1] = 0;
	A.m[2][1] = 1;
	A.m[3][1] = 1;
	ma B;
	B = qpow(1,8);
	A = cal(B,A);//dp[0] ~ dp[9]
	//cout << A.m[3][1] << endl;
	for(int i = 2;i < cnt;i++)
	{
		B = qpow(i,9 * ten[i - 1] - 1);//9-99是90次,99-999是900次,以此类推
		A = cal(B,A);
	}//分区间处理
	B = qpow(cnt,n - (ten[cnt - 1] - 1) - 1);//10的某次方到n
	A = cal(B,A);//一定要尺寸对应,cal(A,B)就是错的
	printf("%lld",A.m[1][1]);
	return 0;
}

T4 P6239 [JXOI2012] 奇怪的道路

click

状压dp,做对一半

维数定义的差不多,设dpi,S,j表示当前在点iiiki+k的连接状态是S,用了j条边

但这样开数组直接RE

后来发现:当前城市往后连(连标号比他大的)等价于后面的往前连(大编号连小编号),反之亦然。所以连的城市只需要考虑我原先想的那个区间的一半,这样的话空间也节约不少

再后来发现第二维状态需要修改,具体的,还是因为按原定义的话连的边的数量难以保证(我们只能从状态得知连没连),所以改进为 iki 这些点的度数的奇偶性,1为奇数,0为偶数

这样一来, dpidpi+1的区间又有重叠,梦回动物园

由于一对城市之间可以连若干条边,所以现在dpi中间转移完(枚举jS),再在dpi,dpi+1之间转移

由于区间重叠,所以直接

dpi+1,S<<1,j+=dpi,S,j

对于dpi内部的转移,我们每次加一条边,每新加一条边,肯定会改变两个点的状态,其中一个点就是i,因为前i1个点连好了,另一个点[ik,i1],要枚举

改变奇偶的话,10,01,想到异或

坑点:ik可能小于0,要和1max,不然30pts

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
int n,m,k;
int dp[35][2050][35];
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	dp[2][0][0] = 1;//1 - 1 = 0无意义,直接从2开始
	for(int i = 2;i <= n;i++)
	{
		int minn = max(1,i - k);//得有
		for(int j = minn;j <= i - 1;j++)//枚举新增边的一个端点,另一端是i 
		{
			for(int v = 1;v <= m;v++)
			{
				for(int s = 0;s <= (1 << (k + 1)) - 1;s++)
				{
					dp[i][s][v] = (dp[i][s][v] + dp[i][s ^ (1 << i - j) ^ 1][v - 1]) % mod;
				}//i - j为枚举的点相对于最右端的位置,i的位置就是i - i = 0,所以是 1 << 0 = 1
			}
		}
		for(int j = 0;j <= m;j++)
		{
			for(int s = 0;s <= (1 << k) - 1;s++)
			{
				dp[i + 1][s << 1][j] = (dp[i + 1][s << 1][j] + dp[i][s][j]) % mod;
			}//区间平移
		}	
	}
	printf("%d",dp[n][0][m]);//答案就是点的度数都是偶数
	return 0;
}

2024.3.24

cnm怎么全是数学cccc

T1 卫星照片

click

bfs染色,得到左上角和右下角,进而得出矩阵大小,暴力检索内部有没有别的,就完了,100pts

#include<bits/stdc++.h>
#define N 80
using namespace std;
int r,c;
char a[N][N];
int vis[N][N];
int cow,house;
int dx[5] = {0,0,0,1,-1};
int dy[5] = {0,1,-1,0,0};
int maxx,minx,maxy,miny;
int chang,kuan;
struct node
{
	int x,y;
};
queue<node> q;
void bfs(int u,int v)
{
	maxx = minx = u;
	maxy = miny = v;
	q.push({u,v});
	vis[u][v] = 1;
	while(!q.empty())
	{
		node tmp = q.front();
		q.pop();
		for(int i = 1;i <= 4;i++)
		{
			int nx = tmp.x + dx[i];
			int ny = tmp.y + dy[i];
			if(!vis[nx][ny] && nx >= 1 && nx <= r && ny >= 1 && ny <= c && a[nx][ny] == '#')
			{
				vis[nx][ny] = 1;
				q.push({nx,ny});
				maxx = max(maxx,nx),minx = min(minx,nx);
				maxy = max(maxy,ny),miny = min(miny,ny);//bfs时求矩形的角
			}
		}
	}
	chang = maxx - minx + 1;
	kuan = maxy - miny + 1;//矩阵大小
}
bool check(int k,int s)
{
	for(int i = k;i <= k + chang - 1;i++)
		for(int j = s;j <= s + kuan - 1;j++)
			if(a[i][j] != '#') return 0;
	return 1;
}//判断是房子还是牛群
int main()
{
	scanf("%d%d",&r,&c);
	for(int i = 1;i <= r;i++)
		for(int j = 1;j <= c;j++)
			cin >> a[i][j];
	for(int i = 1;i <= r;i++)
	{
		for(int j = 1;j <= c;j++)
		{
			if(!vis[i][j] && a[i][j] == '#')
			{
				chang = kuan = 0;
				bfs(i,j);if(!check(i,j)) cow++;
				else house++;
			}
		}
	}
	printf("%d\n%d",house,cow);
	return 0;
}

T2 小魔女帕琪

click

乏了,被这道题爆杀

做期望做多了以为是dp,然后看到109又TM想到矩快加速,然后发现一维dp不够用,又写了二维乱搞搞不出来,最后才意识到是数学推结论题。。。。。。

想起了被列队春游支配的恐惧c

ai=n,即一共能施法多少次

注意到期望中权值乘的概率是一样的:1n!

所以只需要求出权值和

一个施法区间权值为1,那就是求总方案数

首先,一个施法区间排列数为7!

其次,考虑分布位置,这点和列队春游如出一辙,可以得到共n6种分布

再让剩下位置自由排列造不同方案,共(n7)!

还有一点:区间内第i种魔法只释放一次,而一共有aii,所以一个区间由所有晶石释放的方案数是ai

那么

ans=7!(n6)(n7)!ain!

化简可得

ans=7!ai(n5)(n4)(n3)(n2)(n1)n

md

T3 魔法阵

click

拿条件卡枚举范围的题

设一个魔法阵的值分别为A,B,C,D

最暴力是m4,等式可以拿来算B,压到m3可以水70,但是炸了

for(int i = 1;i <= m;i++) scanf("%d",&a[i].val),a[i].id = i,cnt[a[i].val].push_back(i);
sort(a + 1,a + m + 1,cmp);
for(int i = 1;i <= m;i++)
{
	for(int j = i + 1;j <= m;j++)
	{
		for(int k = j + 1;k <= m;k++)
		{
			int A = a[i].val;
			int C = a[j].val;
			int D = a[k].val;
			int B = A + 2 * (D - C);
			if(A < B && B < C && C < D)
			{
				if(cnt[B].size() == 0) continue;
				if(3 * A + C > 4 * B)
				{
					for(int l = 0;l < cnt[B].size();l++) 
					{
						num[a[i].id].a++;
						num[a[j].id].c++;
						num[a[k].id].d++;//这三条当时放到循环外面了,没有和B个数同步,就炸了
						int h = cnt[B][l];
						num[h].b++;
					}
				}
			}
		}
	}
}

现在考虑优化

事实上,上面的优化有一步可以借鉴:使用桶存储出现次数

那么,我们就尝试把所有物品都分到桶里,到时候直接枚举权值而不是具体的哪一个

那么就要讨论枚举的权值的取值范围

我们把等式带入不等式:

6(DC)<CB

t=DC,那么

6t=CB

BA=2t

6t+k=CB,那么可得到

最大差值达到了9t+k,说明9t<n,可以枚举t

枚举D,我们可以得到C=Dt,考虑不同的A,B做出的贡献

显然,只要保证图中的相对关系不变即可形成魔法阵,那么,不妨令k=1,那么6t+1就是一个最小单元,那么当C,D后移时,先前的A,B依旧合法,可以使用前缀和

每一对A,B的贡献都是numA×numBnum表示该权值的数量,再乘以numC,numD即可求出c,d

a,b同理,但是要倒序

这逆天思路是我不配

//这里由于枚举的都是权值,所以a/b/c/d[i]表示当权值为i时对应的a/b/c/d的值
for(int t = 1;t * 9 < n;t++)
{
	int sum = 0;
	for(int D = 9 * t + 1;D <= n;D++)
	{
		int C = D - t;
		int B = C - 6 * t - 1;
		int A = B - 2 * t;
		sum += tong[A] * tong[B];//前缀和累加
		c[C] += sum * tong[D];//计算方案数
		d[D] += sum * tong[C];
	}
	sum = 0;
	for(int A = n - 9 * t - 1;A >= 1;A--)
	{
		int B = A + 2 * t;
		int C = B + 6 * t + 1;
		int D = C + t;
		sum += tong[C] * tong[D];
		a[A] += tong[B] * sum;
		b[B] += tong[C] * sum; 
	}
}

T4 [HAOI2016] 字符合并

click

考场上题都没看懂

k8,状压?

又是区间合并状物,区间dp?

囊括要素法定义dp:设dpi,j,S分别囊括左右端点(区间要素)和状态(状压要素)

接着细化含义:dpi,j,S表示区间[i,j]合并的最高权值,合并后(合并到不能再合并)的状态为S

采用区间的[i,l],[l+1,j]法,枚举断点,那么[i,j]就是[i,l],[l+1,j]的方案和

不妨设定[l+1,j]的长度就是k,考虑到任意长为k的区间合并后不是0就是1,那么合并完后半部分的区间后的新状态不是S<<1(尾巴多个0)就是S<<1|1(尾巴多个1)

那么可以写出方程

dpi,j,S<<1=max(dpi,l,S+dpl+1,j,0)

dpi,j,S<<1|1=max(dpi,l,S+dpl+1,j,1)

接下来关于初始化和特殊情况:

dpi,i,ai=0,最小区间合并结果就是他自己,不需代价

重点来了:有些区间合并若干次后长度正好为k,那么只需要合并一次,不再枚举断点

我们考虑一下这样的区间的长度有什么性质

每次合并相当于砍掉k个补上1个,长度减少了k1,那么最后的长度为k,就满足Len=x(k1)+k,再写一下就是Len=x(k1)+1

那么对于这种区间,加的权值就是wS,对应留下的就是cS,

dpi,j,cs=max(dpi,j,S+wS)

接下来是超级细节环节

  • 断点枚举

由于合并的区间不重叠,那么每次就要跳过已合并的部分

l -= (k - 1)
  • 状态的枚举

就是

for(int l = j - 1;l >= i;l -= (k - 1))
{
	for(int s = 0;s <= ?;s++)
	{
		dp[i][j][s << 1] = max(dp[i][j][s << 1],dp[i][l][s] + dp[l + 1][j][0]);
		dp[i][j][s << 1 | 1] = max(dp[i][j][s << 1 | 1],dp[i][l][s] + dp[l + 1][j][1]);
	}
}

Len<k时就没必要跑到2k1,跑到2len1即可,注意这里的len是剩下的合并不了的,也就是对k1求余过的,这里模数为0时要改成k1不然会炸循环

int r = 余数; 
if(r == 0) r = k - 1;
for(int l = j - 1;l >= i;l -= (k - 1))
{
	for(int s = 0;s <= (1 << r) - 1;s++)
	{
		dp[i][j][s << 1] = max(dp[i][j][s << 1],dp[i][l][s] + dp[l + 1][j][0]);
		dp[i][j][s << 1 | 1] = max(dp[i][j][s << 1 | 1],dp[i][l][s] + dp[l + 1][j][1]);
	}
}

此时进行的是一个初始化操作(把一些极小值变成0),这一点会影响求余方式的选择

  • 求余

进入特别dp的条件是mod(k1)=1,那么就衍生出了两种方法(其中if:r=0,r=k1)

r=len%(k1),if(r==1)...

r=(len1)%(k1),if(r==k1)...

选择哪一种呢?

特殊dp中的状态是02k1所以02k1都要初始化掉,即r=k1

显然是第二种求余方式

  • 特殊dp

这里不能直接按照dp式来写,会65pts,要开临时数组,因为S会枚举到0,1,导致用dpi,j,0/1更新dpi,j,1/0,肯定不对

复杂度大概是个O(n22k)

for(int i = 1;i <= n;i++)
	for(int j = 1;j <= n;j++)
		for(int s = 0;s <= (1 << k);s++) dp[i][j][s] = -inf;
for(int i = 1;i <= n;i++) dp[i][i][a[i]] = 0;
for(int len = 2;len <= n;len++)
{
	for(int i = 1;i + len - 1 <= n;i++)
	{
		int j = i + len - 1;
		int r = (len - 1) % (k - 1); 
		if(r == 0) r = k - 1;
		for(int l = j - 1;l >= i;l -= (k - 1))
		{
			for(int s = 0;s <= (1 << r) - 1;s++)
			{
				dp[i][j][s << 1] = max(dp[i][j][s << 1],dp[i][l][s] + dp[l + 1][j][0]);
				dp[i][j][s << 1 | 1] = max(dp[i][j][s << 1 | 1],dp[i][l][s] + dp[l + 1][j][1]);
			}
		}
		if(r == k - 1)
		{
			maxx[0] = maxx[1] = -inf;
			for(int s = 0;s <= (1 << k) - 1;s++) 
			{
				maxx[c[s]] = max(maxx[c[s]],dp[i][j][s] + w[s]);
				dp[i][j][0] = maxx[0];dp[i][j][1] = maxx[1];
			}
		}
	}
}
ll ans = -inf;
for(int s = 0;s <= (1 << k) - 1;s++)
	ans = max(ans,dp[1][n][s]);

2024.4.5

今天没带脑子,不知道为啥老想睡,再加上题不好搞,就gg了

后两道题完全跟概率和期望没啥关系,顶多就是做个除法,T3组合,T4数据结构(听说优化暴力也行)

P4550 收集邮票

click

还算正经考期望的

看形式想到Jon and Orbs,然后就被带偏到二维dp上去了,没救了

其实后来想想也确实不一样,那道题次数题目说了是收敛的,所以可以范围搞大点,这题次数没说有啥性质,得维护

然后倒序(取了i种,取完剩下的)

设从i张到n张要ki步(不是期望)

对于次数,有in的概率取到同一种,剩下的概率取到新的

gi=E(ki)=ingi+ningi+1+1

对于价格,结合每次的价格等于是第几次,由期望原始定义

ansi=E(ki(ki+1)2)=E(ki2+ki2)=E(ki2)+E(ki)

hi=E(ki2)

因为

E((ki+1)2)=E(ki2+2ki+1)=E(ki2)+2E(ki)+1

再结合有in的概率抽到旧的,剩余概率抽到新的,所以

hi=inE((ki+1)2)+ninE((ki+1+1)2)=in(hi+2gi+1)+nin(hi+1+2gi+1+1)

答案就是ans0=h0+g02

for(int i = n - 1;i >= 0;i--)
{
	g[i] = g[i + 1] + n * 1.0 / (n - i);
	h[i] = ((2 * g[i] + 1) * i * 1.0 / n + (h[i + 1] + 2 * g[i + 1] + 1) * (n - i) * 1.0 / n) * n * 1.0 / (n - i);//要移项
}

还有一种办法是按照分手是祝愿那题分析,不同的是没有“抽错”的代价

P4397 [JLOI2014] 聪明的燕姿

click

我们知道

sum=j=1k(i=1αj(pji)+1)

那么pjαj就是答案的一个因子

暴力枚举质数会炸,那么考虑搜索

每次枚举i=1αj(pji)+1,能够整除时进入下一区块并用pjαj更新符合条件的数

void dfs(int now,int id,int num)
{
	if(now == 1)//不能解分解
	{
		ans[++res] = num;
		return;
	}
	if(isprime(now - 1) && now > pri[id]) ans[++res] = num * (now - 1);//形如1+p的区块
	for(int i = id;pri[i] * pri[i] <= now;i++)
	{
		//cout << 114 <<endl;
		int base = pri[i];//p_j^(a_i)
		int sum = pri[i] + 1;区块总和
		for(;sum <= now;base *= pri[i],sum += base//更新最高次幂和综合)
		{
			if(now % sum == 0) dfs(now / sum,i + 1,num * base);//进入下一区块并用最高次幂更新答案
		}
	}
}
//记得要给ans数组排序

P4492 [HAOI2018] 苹果树

不好好考概率的题

是的,因为你会发现所有树的出现概率都是1N!,那就只需要得到所有树的总距离之和就行了

求这个东西是组合(CaO)

我们不从点入手,从边开始,考虑一条边的贡献

对于edge(u,v),当v为根,大小是sizv(包含v)的子树内的点向外走时都会经过这个边,那么贡献就是sizv(nsizv)

我们可以枚举v,sizv来累计答案

接下来考虑子树内和子树外点的分布(编号和形态)

对于以v为根的子树来说,形态上有sizv!种(和得到概率为1N!一样的方法),再结合先有根再有儿子,v的儿子们的标号肯定[v+1,n]
从中选择的方案有Cnvsizv1种(除去根),所以内部方案就是sizv!Cnvsizv1

对于子树外部,根节点为1不变,剩了nsizv1个点,长出1v这一部分有v!种形态,结合这些点都不在v的子树内,我们发现,1v1这些点都可以作为连接点并不断延伸下去,方案是(v1)v(v+1)(nsizv1),那么总方案就是v!(v1)v...=v(v1)(nsizv1)!

所以

ans=v=2nsizv=1nv+1sizv(nsizv)sizv!Cnvsizv1v(v1)(nsizv1)!

//用递推法求组合数
for(int i = 2;i <= n;i++)
{
	for(int siz = 1;siz <= n - i + 1;siz++) 
	{
		ans = (ll)(ans + siz * (n - siz) % p * jie[siz] % p * C[n - i][siz - 1] % p * i % p * (i - 1) % p * jie[n - siz - 1] % p) % p;
	}
}

P2221 [HAOI2012] 高速公路

又一道披着概率外壳的2B题

对于每次询问,分母就是len(len1)2,所以只需要维护分子,到时候同除以一个gcd就完了

所以本质还是维护区间修改和区间和

暴力最高水40(这是蒟蒻水平,有人能直接水过去)

那就上数据结构呗

不想写线段树

和上道题一样,考虑一条边会被走几次

后面为了方便,边(i,i+1)存在vali

对于(i,i+1),左边有il+1个点(li),右边有ri个点(i+1r)所以一次查询的答案就是

i=lr1vali×(il+1)(ri)

拆开

(l+r1)i=lr1ivali+(rlr)i=lr1valii=lr1i2vali

维护ivali,vali,i2vali的区间和就行了

对于修改(举个例子):

i2(vali+Δval)=i2vali+i2Δval

加的是后面一部分

又臭又长的代码就不放了

2024.5.2

T1 时间复杂度

click

大模拟,细节题

考场上挂了50是因为O(nw)w处理挂了

然后改到80多就改不动了

这种题没数据就看不出问题

目前最高record

T2 Emiya 家今天的饭

click

卫宫教的

考场上看出和组合有关,没搞出式子,骗了n=2就滚蛋了

有些性质get到了

  • 每行只能选一个框

  • 每列选的框数不超过总菜数一半

发现第二个性质不好维护,列与列之间微调一下就成了新方案

但是他的反面却很简单:最多只有一列框数会超过一半

所以考虑容斥

那么可以枚举不合法列,然后总数-总不合法就行了

假设当前枚举第c列不合法,那么这一列的框数一定比其他所有列的框数加起来还要多

结合行与行之间相对独立,可以使用dp

dpi,j,k表示当前在第i行,第c列选了j个,其他列选了k个,再设sumi为第i行的总和

因为一行只能选一个,所以这一个要么在c列上要么在其他列上

c列上,选的就是ai,c,和dpi1,j1,k搭配

不在,则除c列外每一列的框都能和dpi1,j,k1搭配

还可以不选,累计上一行状态

所以

dpi,j,k=dpi1,j,k+dpi1,j1,k×ai,c+dpi1,j,k1×(sumiai,c)

c列的非法总数就是

j>kdpn,j,k

总数:每一行可选总数为sumi,算上不选是sumi+1,则总数为

i=1n(sumi+1)

那么合法的就是1,这里的-1是啥也没干的情况

把m打成n都有60就很难评

record

接下来优化,肯定是压dp的维数

发现后两维一定满足j>k,故不妨合成一个jk的维度,记为l

那么

dpi,l=dpi1,l+dpi1,l1×ai,c+dpi1,l+1×(sumiai,c)

其他的不变

考虑到维护差值,就要平移防止下标为负

坑点:初始化也平移了,dp0,N=1

record

T3 P7098 [yLOI2020] 凉凉

click

好名字

dfs应有35,但我没有

状压铁路修没修状态,但不知道深度和铁路状态咋搞

后来一看,全TM是预处理了

  • i条地铁修在j深度的花费 vali,j

  • 是否相撞的visi,j

  • 在深度i修建状态为S的花费wi,S

对于w的更新稍微说一下:枚举S,把S中涉及的地铁拎出来,用vis判状态是否合法,不合法值就是inf

然后就是超简单dp:

dpi,S|s=min(dpi1,S+wi,s)S&s=0

得到一个record

T的原因是dp中的大小S复杂度来到22n=268435456,非常吓人

这时有个巧妙方法:我们可以枚举S的子集和对应补集来更新S

for(int s = S;s;s = (s - 1) & S)
	dp[i][S] = min(dp[i][S],dp[i - 1][S ^ s] + w[i][s]);

这样就过了

T4 [联合省选 2020 A]树

click

想水链的20%,没水到

可以使用Trie数维护异或和,但也有没用的

用的差分(??!?!?!?!)

规定:ui级的祖先表示深度相差i的父节点

我们发现,深度增加(减少时),就是所有的d增加(减少)1,即所有异或的数字增加(减少)1

所以重点就是维护整体变化事的异或和,这样就能从子节点出发计算贡献

由于是位运算,考虑按位计算贡献

类比十进制或是手算可得:改变百位需要加102,那么二进制中改变第k(0)位就是加2k

进一步的,k位的变化随着加1呈现循环,每个循环节中有2k02k1,长度2k+1

再把+1和深度对应

也就是说,异或某点的时候,对于第k位,该点的

[a+2k+1×j,(a+2k1)+2k+1×j],j[0,...]

级祖先对应的第k位都是1(即给祖先异或一个2k ),其中a是循环节中第一个1的位置,区间形式可类比三角函数单调区间

每次+1就只对这些区间有影响,修改这些区间可以使用差分

具体的,对上面区间的左端点和右端点+1分别异或2k,取通式就是a+2k×j

但区间又有一大堆

但再看通式就发现a是不变的,即mod2k一样,不妨从此处下手

sk,a就表示mod2k=a的一系列点,再观察可得这些点的深度都是有规律的

后续操作及代码参照第一篇题解

太吊了我一定写不出来

然后看看一般的Trie树方法

2024.5.3

发现现在思维痼疾一堆,没救了

T1 Stack of Presents

click

思路想到了

考虑到放回去时可以改变顺序,因此对于每次需要挪动前面礼物的位置pos,如果送礼序列中连续若干个礼物的位置在pos前,则能够通过放回时的排列使得这些礼物对时间的贡献均为1(连续从栈顶取)

本来记录个pos就行了,但好像习惯性的写了个循环上去。。。

while(t--)
{
	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
	last = 0;//上一个需要挪动前面礼物的位置
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++) scanf("%d",&a[i]),pos[a[i]] = i;
	for(int i = 1;i <= m;i++) scanf("%d",&b[i]);
	ll ans = 0;
	int cnt = 0;//已经送出的礼物数
	for(int i = 1;i <= m;i++)
	{
		if(pos[b[i]] < last) ans++,cnt++;//在挪出位置前,可以通过改顺序使其贡献为1
		else ans += 2 * (pos[b[i]] - cnt - 1) + 1,cnt++,last = pos[b[i]];
	}
	printf("%lld\n",ans);
}

T2 棠梨煎雪

click

在某oj上暴力碾了std可海星

如果有k列全是问号,答案就是2k

而且如果有一列有0又有1,肯定没戏

暴力就是枚举每一列和lr排除没戏的情况,O(nmq)应有45pts

优化也是优化这里并将两种操作合并

区间查询可以使用线段树+位运算,大不了就是开30棵

对于问号位,我们只关心这一列有没有已知元素,因此出现问号时,问号赋为0,已知赋为1,没问号的列肯定还是要赋成准确值

由于已知出现一个就确定了这一列,所以使用 | 运算

代码可参照这位巨佬的

T3 打砖块

click

dp

本来按坐标dp整的,结果状态就不对,处理子弹分配的时候乱的一p,就死了

还把本来是预处理的操作曰到dp初始化里了

考虑子弹只会竖着走,所以把每一列打包,预处理搞

定义sumi,j表示第i列花费j颗子弹时的得分,dp定义同理

然后这题的坑就在Y

在打包列的时候,会把Y宏观等价认为是子弹没有损耗白挣分,但这样有失偏颇

  • 得有子弹去打Y

  • Y得到的子弹不一定继续沿着这一列打,样例就是

结合第二条,就有必要在dp和预处理中区分是否在这一列打光子弹,且打Y时得到的子弹是否继续在这列使用

因此,记sumY表示从Y得到的子弹继续在该列使用,此时等价认为生效

再记sumn表示相反,此时等价认为无效

拿样例来说,sumY2,1=7,相当于认为只消耗了一发就打碎下两个块

sumn2,1=2sumn2,2=7,相当于认为一发就是打了最下面,两发就是打了下两块。至于Y的奖励,拿去打第一列了,所以打第二列下两块就是用了两发

那么这样一来就能模拟第二条了

相应的把dp新增一维表示在这列打到了Y(1)还是N(0),或者说是等价认为是否生效

dp[i][j][0] = max(dp[i][j][0],dp[i - 1][j - l][0] + sumY[i][l]);//整合Y 
if(l > 0) dp[i][j][1] = max(dp[i][j][1],dp[i - 1][j - l][0] + sumn[i][l]);//第i列收尾, 奖励给j-l那部分留着,此时前面停到了N 
if(j > l) dp[i][j][1] = max(dp[i][j][1],dp[i - 1][j - l][1] + sumY[i][l]);//第i列收尾,l颗和奖励都梭哈到第i列,那么对于j-l部分来说j-l发也是全梭哈的 

坑点:

1.sumn的更新用的是sumY,相当于把连续Y的权值赋到这一串Y下面的N

if(vis[i][c])
	sumY[c][cnt] += a[i][c];//等价认为不消耗
else
{
	++cnt;//新增一发
	sumY[c][cnt] = sumY[c][cnt - 1] + a[i][c];
	sumn[c][cnt] = sumY[c][cnt - 1] + a[i][c];
}

2.答案不比大小,就是dpm,k,1

T4 斗地主

click

考试的时候先水了20,然后认识到是搜索,但没时间写了

根据经验来说,肯定先挑一次出牌多的方法出(当然也不一定,所以搜索而非纯贪心)

因此先看顺子,再看带牌

还有一点就是,对牌,三牌,炸弹什么的其实不用特意判断看,因为每种牌最多不超过四张,出法还都是单一数值(没有组合),所以只需最后遍历一遍,还有剩的话再出一次就行了

record

2024.5.19

概率期望组合乱杀赛

T1&T4 P5516 [MtOI2019] 小铃的烦恼

click

矩阵没有意义,因为每个都是1.0

有点像分手是祝愿,但又很不一样

xi表示某颜色(不妨设为A)从i个改成n个的期望步数

一个操作分两大类三种情况:

  • 把一个颜色改成A ,花了1步,再花xi+1步即可
  • A改成别的,同上,共花了1+xi1
  • A无关

设修改涉及到A的概率为k=2i(ni)n(n1)

对于第一大类,差别只在于(A,B)还是(B,A),所以两种情况各占12,且都涉及了A,所以系数是k2

对于第二大类,没有修改A,系数1k

考虑到所有的都是操作了一步,所以有式子

xi=k2(xi1+1)+k2(xi+1+1)+(xi+1)(1k)

化简得到

xi=12xi1+12xi+1+1k

然后根据经验可知这东西退化成dp

就完了?

没有

事实上,还存在着改着改着A就没了,所以还得加条件

pi表示能把Ai个改成n个的概率

还是上面的两大类,再把修改A的概率设为k,对于第一大类,对应的概率都是k,对于第二大类,对应的概率就是12k,就有

pi=kpi+1+kpi1+(12k)pi

化简得到

pi=pi+1+pi12

这是个等差数列,再结合定义得到

p0=0,pn=1

可以算出

pi=in

那么根据条件概率,所有的步数都要乘上对应的可行概率,所以得到

pixi=12pi1xi1+12pi+1xi+1+pin(n1)2i(ni)

再代入概率表达式

inxi=i12nxi1+i+12nxi+1+n12(ni)

这样的话,再根据退化成线性,就可以跑一个高斯消元板子的简化版求出x,然后还要补上条件概率(只存部分系数一把弄下来)

但是有点麻烦,还可以设dp=x×p,那么答案就是dp

那就会有

dpi=12dpi1+12dpi+1+n12(ni)

dp0=dpn=1

dpidpi1=dpi+1dpi+n1ni

相当于每次的差就是给上一次的差加一个数,特殊的是,最初的差(没加数)就是dp1dp0=dp1,所以可以求出dp1

dp0dpii个差,且每次加上的最后一项还会累加,所以

dpi=dp0+idp1+j=1i1(ij+1)n1nj

i=n(因为dpn=0

dpn=ndp1(n1)(n1)=0

得到dp1=(n1)(n1)n

然后就递推了

T3 [JXOI2018] 游戏

click

组合

第一次读错题了,后来才发现是怎么回事

就是2,4,3这组排列

虽然提醒2会使得4会工作,但是3没有工作,所以为了检查3,还得走4再走3,所以时间是3

这也就是说:时间就是从头开始,一直到最后一个未被提醒的位置

显然,未被提醒就是前面没有自己的因子,我们可以把这类数预处理出来,假设有k

那么问题就变成了一个期望问题:最后一个数的位置(相对于开头)是权值,与落到该位置的概率相乘并求和,再给最后的答案乘以n!(消去概率,很显然所有概率分母都是这个)

对于一个特殊数,它可能的最后位置应该在[k,n],假设在i

那么同时,剩下的k1个数一定都在前面1i1,这样的分布有Ci1k1

总的分布是Cnk,那么分布上的概率就是Ci1k1Cnk,权值是i

所以

ans=n!×i=kn(Ci1k1Cnk×i)

尝试化简:

n!×Ci1k1Cnk×i=n!×(i1)!(k1)!(ik)!×k!(nk)!n!×i=i!k!(nk)!(k1)!(ik)!=i!(k1)!(ik)!×k!(nk)!=i!k!(ik)!×k×k!(nk)!

所以

ans=k×k!(nk)!×i=knCik

最后一个西格玛很熟悉了,就用公式Cnm=Cn1m1+Cn1m,补一个不存在的Ckk+1,答案就是Cn+1k+1

所以

ans=k×k!(nk)!×(n+1)!(k+1)!(nk)!=kk+1(n+1)!

ps:代码实现时用埃筛思路即可,但为了速度要把标记数组开成bool

T2 SSY的队列

click

70pts的状压,std的记搜

状压:0/1表示是否进入队列,设dpi,S表示最后一个人是i,状态为S是的方案数,答案是i=1ndpi,2n1

码子

接下来通过优化状态,我们可以得到如下性质:

  • 我们可以按照膜m的余数将所有数字分类,则不同类的数字相邻一定合法,考虑到同一类的数等价, 那么答案就是对类的排列方案数乘上每个类所含数个数的阶乘,设fk,a1,a2,...an表示当前最后一个数为第k类,各类数用掉了a1,a2,...an

  • a1,...an顺序无关,即对于i,j,ai=aj,则fi,a1,a2,...,an=fj,a1,a2,...,an,这个性质可以用来使得a1a2...an,减少了状态数

参考代码

2024.6.2

T4 围栏障碍训练场

click

dp老想不起来写预处理啊啊啊

状态都想好了,dpi,0/1表示走到i号篱笆的左端/右端的最小步数

然后每次更新都要用上一次的左端点和右端点来更新

这个“上一次”就是预处理的内容(不好处理也可以类似的处理“下一次”)

然后没写,硬存每次新的左右端点,拉了一大坨才40

考虑到预处理是n2的,难说过不过

预处理“下一次”,dp意义稍有不同

啊,T

再优化的话,可以使用区间覆盖+单点查询,即每个区间把自身范围内的高覆盖成自己所在高度,然后对于端点,每次查询一下就可以了

要写线段树,like this

T1 等差子序列

click

想着是用差分数组乱搞暴力,但是还有更简单的

枚举差值得数值,再判断一下位置单调性和数值单调性是否同步就可以n2水过去

注意的是单调性有增有减

record

T2 [Jsoi2015]非诚勿扰

click

我们先计算一下对于第i个女人,她的列表内第j个男人被选中的概率

以第一个人为例,他被选中的概率 = 直接被选中的概率 + 列表循环一次后被选中的概率 + 列表循环两次后被选中的概率 + ...

设列表长度为leni,那么每次的概率就是前面的人都选不上乘以自己被选上,即

P=(1p)0p+(1p)lenip+(1p)2lenip+...=i=0inf(1p)ilenip

然后

P(1p)leniP=p

P=p1(1p)leni

类似的,就能得到第j个男人被选中的概率为

Pi,j=(1p)j1×p1(1p)leni

对于一对女人(p,q),如果一对男人(j1,j2)符合条件,那么贡献就是Pp,j1×Pq,j2,所以该对女人的贡献就是所有符合条件的(j1,j2)的概率乘积和

观察要求形式,可以使用树状数组实现类似求逆序对的方法维护

like this

T3 [CSP-S 2021] 括号序列

click

考虑使用题目给的规则扩展,所以是区间dp

定义dpi,j,id表示[i,j]字符状态为id的方案数,其中对id有如下规定

  • id=1:形如 (...) 的字符串

  • id=2:形如 ***..** 的字符串

  • id=3:形如 (...)**..*(...)的字符串,即左右均为括号

  • id=4:形如 (...)*...*(...)**的字符串,即左边为括号,右边为 *

  • id=5:形如*..*(..)的字符串,即左边为*,右边为()

  • id=6:形如 *..*(..)***的字符串,即左右均为 *

接下来搞dp

  • dpi,j,1=dpi+1,j1,2+dpi+1,j1,4+dpi+1,j1,5+dpi+1,j1,3,前提是i,j能配成一对括号

    • 含义是括号内除了两边是星星的情况不合法其他均可
  • dpi,j,2特判即可

  • dpi,j,3=k=ij1(dpi,k,3+dpi,k,4)×dpk+1,j,1+dpi,j,1

    • 含义是第3,4种满足左边为括号开头,然后右边补一个括号,是情况1,还有一种特殊的是整个是一个括号序列
  • dpi,j,4=k=ij1dpi,k,3×dpk+1,j,2

    • 含义是在第3种情况后面加一串星星构成第四种情况
  • dpi,j,5=k=ij1(dpi,k,5+dpi,k,6)×dpk+1,j,1

    • 含义是开头为星星的为第5,6种情况,在结尾补一个星星成为情况5
  • dpi,j,6=k=ij1dpi,k,5×dpk+1,j,2+dpi,j,2

    • 含义是在开头为星,结尾为括号的情况后面补一串星得到第六种情况,特别的,全是星属于该情况

答案为dp1,n,3

code

2024.6.9

T1 小奇挖矿2

click

还没悟透,还得悟

O(m)dp60

这道题有一个很○○的性质:如果两个行星之间的距离18,那么这从一个星球一定可达另一个星球

得到这个性质可以打表,也可以用一些奇奇怪怪的数学定理(因为此时距离可以写成4a+7b的形式,应当和不定方程有关)

这样,我们就可以离散化,把位置从星球编号变成距离,如果两星球间距离大于十八,就只给距离加18就行了

注:

  • 方便起见可用倒序

  • 别老惦记着map,会被卡到70

code

T2 小奇的矩阵(matrix)

click

式子都有了

n+m1=k,sum=i=1n+m1Ai,则

=1k(kAisum)2=1k((kAi)22kAi×sum+sum2)=sum2+(kAi22Ai×sum)=sum2+kAi2+2×sumAi=kAi2sum2

接下来就要维护平方的和以及和的平方

然后就到了本蒟蒻必翻车环节:可以把和扔进状态枚举,反正最大才(30+301)30<1800

所以定义dpi,j,S表示走到了(i,j),元素和为S时的平方和

那么当Sai,j时就从左边和上边转移即可

初始化:dp1,1,a1,1=a1,12

code

后面两道都和树有关,难绷

T3 小奇的仓库(warehouse)

click

先不管异或,用树剖求有30

稍微解释一下solve:对于父子节点u,v和边(u,v)v到自己子树内不走这条边,sizv,到u其他子树和u外面部分都要走这条边,+(nsizu)+(sizusizv)

然后看看异或

观察到M15,也就是说异或顶多影响后四位,所以可以暴力单独维护

T4 Kamp

click

树型dp里的题但我跳了

树形dp通常都分树内和树外两种情况。所以往往变量成对

对于此题,我们可以定义fu表示把u子树内的人送完的花费,gu表示把u外面的人送完的花费

那么对于边(u,v),如果v内有人,那么就要进入v的子树送完人后再出来。所以fu=fv+2×W(u,v)

类似可得g的计算方法:

gv=gu+(fufv)

特别的,根没有g

那么,对于以点x为起点(根)的情况,答案自然就是fx+gx····吗?

我们回到fu的计算方式,可以发现每条边都算了两次,也就是说 fu表示的是送完u内的人并且回到u点的花费

g也一样

但实际上,送完最后一个人后没必要回来,所以有两种选择

  • u外再u内,停在u内不回来

  • u内再u外,停在外面不回来

每种方案都是在原来和的基础上减掉一个回来的花费

那么减掉的肯定越大越好,所以就是求以u为起点的最长链,内外两条,取大的减掉

最后有一个细节:计算g的时候,没有算上(u,v)这条边,但也不能直接加一个二倍,如果v内有人,就不加了,因为fu把这条边算过两遍了

更多见注释

2024.7.6

T1 公式求值

根据样例写出来发现每一位都是个前缀和,搞一搞就行了

code

T2 最长的Y

60pts:枚举每个连续的Y,左右寻找Y看能不能合并,指针法

code

std:

考虑初中学过的min(|xa|+|xb|+...)这类问题就能得到:最终的一段Y必然是往中间位置汇聚得到的,因为这样最优

然后二分区间按上面类似的方法搞就完了

有一点就是O(1)得到距离的预处理:

设第iY下标为p,那么[1,p]内点点的个数就是pi,那么要挪到第j个下标为qY旁边,代价就是两个Y中间的点点数,即|(pi)(qj)|

可以通过维护前缀和等方法得到距离

code(含注释)

T3 交换序列

T0蛋的dfs改写成bfs30 ???

接下来考虑另一种方法:不妨枚举加入的字母来合成答案串,再判断一下符不符合条件就行了

合成的串可以使用类似状压的方法压缩表示,有60

code

想到这里就基本快到正解了

这里有一个隐藏限制:klen(len1)/2

理由很简单,超过这一个之后就可以对序列全排列了,多出来的交换次数没什么用

那么就可以把它扔进dp

定义dpi,j,k,l表示用了iK,jE,kY,交换了l次的方案数

预处理原串中三个字母的数量、位置等,通过枚举新加入的字母来转移答案

code

T4 最长路径

由于数据规模小,所以不妨直接枚举第k大的数tmpnm

定义dpi,j,l为走到(i,j),经过了ltmp的数,如果ai,j严格大于tmp,必选,如果严格小于,必不选,等于的话就是可选可不选

但是样例二说明有些相等的情况必须取

那就不妨都先取上,然后再跑一边不取的情况看看能不能更优就行了

五次方的复杂度能过就是了

code

2024.7.8

5道题的一天

T1 分糖果

按余数分,有000,111,222,012四种

那么就有两大种方案

  • 尽可能多的凑012,剩下的凑000,111,222

  • 尽可能多的凑000,111,222,剩下的凑012

两种分别算求max即可

把j+2写成j + 没删调试痛失100

code

T2 乒乓球

30pts:暴力O(n)

整洁:

考虑周期 (废话)

大周期就是两个人的比分再次相同的中间经过的部分

但是要注意的是:不是一开始就进入周期

这里用到一个技巧:k个球结束时,如果两人分数都超过11还没分胜负,那么就可以在维持分差不变的情况下缩小比分在11以内

那么比分最多$12 \times 12 k$就肯定能找到周期

找到周期后,由于每个周期满足两人赢的局数增量一样,且比分不变,所以可以跳过不少

再把首位未进入周期的部分暴力掉就行了

code

T3 与或

开的ll但scanf %d 痛失暴力分30

这里有一个策略:把|放&后边,值一定不变小

好像也很好理解,x&y|z最小是zx|y&z最大是z

所以直接把所有|放到最后得到最大值

然后枚举前面,看能不能在不改变值的情况下放|,形成|||..&&&..|..|的样子

这里,为了快速得到||||或者&&&&&的答案,要预处理一个位前缀和,再根据特性搞

code

T4 跳舞

有暴力,但感觉不好打,有用到状压

看数据范围像是区间dp,但最后竟然只是个预处理?!

oki,j表示能否把[i,j]内的人都消掉,为了实现这个,可以通过找i1,j+1号人跳舞实现

oki,j=ork=ij(oki,k1&okk+1,j&([gcd(ai1,ak)>1]|[gcd(ak+1,aj+1)>1]))

然后定义dpi表示对于前i个人,留下第i个人,最多能消掉多少人,那么

dpi=maxj=1i((dpj+ij1)×oki+1,j1)

这里要dpn+1,因为第n个人也不一定留着

code

T5 音乐播放器

很熟悉了

肯定要做01背包

如果第i首歌结尾,那么它就不能在前面出现,所以要用剩下的音乐填满[Sai,S)这一部分

接下来根据样例,我们需要计算如果前面出现了j首不同的歌曲(算上最后一首一共j+1首不同的),对应概率是多少(补充:放了x首,剩下的新歌概率都是1nx

这里可以借鉴非诚勿扰的等比数列方法:

P(j+1)=1n×k=1jkn×(1+j+1kn+(j+1kn)2+...)=jn×nn1×j1n×nn2×...×1n×nnk×1n=k!(nk1)!n!

此处介绍另一种方法:

..

2024.7.9

T1 超市抢购

只要当前不够就从后一个搬,哪怕后一个也不够,反正后面会补齐的

ll ans = 0;
for(int i = 1;i <= n;i++)
{
	if(delt[i] < 0)
	{
		ans += abs(delt[i]);
		delt[i + 1] -= abs(delt[i]);
	}
}
cout << ans; 

T2 核酸检测

dp

定义dpi表示在第i时刻喊了一声,并且让所有li的人下来做核酸的最少方法数量

转移就是dpj=mini<j(dpi+1)

接下来考虑j的条件

根据dp定义,新加的1肯定是在j时刻喊的,那么[i+1,j1]中间就没喊,所以要保证没有区间完整的处于其中

那么就要保证开头在这个区间内的区间的右端点都大于j

可以预处理一个Ri表示以i为开头的区间中右端点的最小值,那么jmink=i+1j1Rk。写在码里就是更新循环上界

由于要覆盖最后一个区间,所以在l,r的最大值中找答案

接下来考虑求方案

如果dpij有贡献,就累计i处的方案,否则继承

code

T4 龙珠游戏

屌题

数据明示区间dp,但是神龙的取法会使得区间不稳定(它随便取),要转化

明确的一点是,如果神龙拿走的珠子不在端上,其实可以选择“不取”,反正小明取不到

那么怎么弥补没取的次数呢?

我们可以让神龙在某个时刻从左(右)端连续取走若干个珠子

这样就把两人的操作都限制在端点处,可以搞一搞dp

定义xmi,j,k,loi,j,k分别表示小明/龙在决策区间为[i,j],龙能连续取k次的最大/最小总和(神龙希望小明得分少,所以对应最小)

那么分类

小明:必须取左/右端,此时龙决策完了

龙:

  • 取左/右端,消耗一定次数

  • 放弃,继承小明的状态

就有方程

xmi,j,k=max(loi+1,j,k+1+ai,loi,j1,k+1+aj)

loi,j,k=min(xmi,j,k,loi+1,j,k1,loi,j1,k1)

答案:xm1,n,0

code

T3 七龙珠

竟然用背包我服了呀(想到了不敢写)

后面写对的话瓶颈就是背包的O(xiM)

突破以后再说,要用科技,不用上限80

那么背包出来可以拼出的合法龙珠后从大到小排序(系数为负的话从小到大,注意记得算上能量值为0的龙珠)

有两种写法

  • bfs

最大的肯定是每组最大值,记为(1,1,1,1,1,1,1),那么次大的就是(2,1,1,1,1,1,1),(1,2,1,1,1,1,1)...,再往后就是(1,2,2,1,..)..,妥妥的bfs,每次给单个组的排名加一,把值扔进优先队列搞

重点是去重,想办法搞搞,比如hash什么的

code

  • dfs

目前有个剪枝就是排名aiai就是上面括号里的东西,这个东西比k大肯定没戏

能拿50

2024.7.10

O(n)能过1e8!

O(n)能过1e8!

O(n)能过1e8!

T1 算术求值

模拟出kx+b枚举x就行了

......

T2 括号序列

先把能配上的扔掉,然后相邻两个一组按下列方式计算代价

  • "((""))":代价为1

  • ")(":代价为2

完了

code

T3 Count Multiset

dp

可以做背包搞,有40

ll dp[N][N][N];//dp[i][j][k]: 考虑前i个数,有j个不为0,和为k 
int n,m;
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin >> n >> m;
	dp[0][0][0] = 1;
	for(int i = 1;i <= n;i++)
		for(int j = 0;j <= n;j++)
			for(int k = j;k <= min(i * j,n);k++)
				for(int o = 0;o <= min(m,j) && o * i <= k;o++) //枚举重复元素数量,重复的就是i 
					dp[i][j][k] += dp[i - 1][j - o][k - o * i];
	for(int i = 1;i <= n;i++) cout << dp[n][i][n] << endl;
	return 0;
}

补上取余能到70

std:

继续沿用补0的思想,定义fi,j表示考虑了前i个数,和为j,那么有两种转移

  • 补一个0,从fi1,j转移

  • 整体加1,从fi,ji转移

这里巧妙的是通过补0和整体加构造重复元素,避免了枚举

那么考虑限制条件,很显然第一种操作最多补m0,那么贡献就是k=1mfik,j,但这样相当于规定了序列里一共只有k0,则要求前ik个数中不能有0,这在f的定义中是看不出来的

所以补充一个gi,j表示序列里没有0的情况数,这个的转移就是整体加一的情况,所以就有

fi,j=fi,ji+k=1mgik,j

gi,j=fi,ji

西格玛可用前缀和优化

code

T4 选数

dfs

分别把质因子给x,y去搜,然后要枚举幂次

为了防止重复有一些特判

朴素版本35,加上大样例有40

剪枝:考虑到要保证个数最优,肯定大质数的幂次都是1,那么不妨把当前(x,y)不断和后面质数相乘且只乘一次直到上限,如果这样都达不到目前的最优解,就不用搜了

95+大样例 = 100(bushi)

T5 划分序列

之前有一道类似的

二分最大值,那么两种情况的最大值都不超过二分的值

接下来定义dp

考虑到有两种代价计算方式,如果分别dp,可能不从同一处转移

这里巧妙的是,把一种方式作为转移条件

于是,把区间和作为限制

定义dpi表示取了i,并且满足区间和小于二分值的最小代价(单点和)

那么dpi=minj<i,sumi1sumj<x(dpj)+ai

前面的单调队列压线性

code

2024.7.11

上强度了

T1 最短路

数据范围明示Floyd,但是多了点权

考虑到Floyd中有枚举中转点的操作,不妨用这个

将数组排序,就可以保证 ik,kj这两条路径的最值点只可能在i,j,k中,注意这里的i,j,k不是点,而是排完序后数组的下标

还有一点就是点权不能混进dis数组搞,要新开一个数组

code

T2 方格取数

为什么有人(Byj大神)四重for有90啊卧槽

各种卡时如枚举长宽,其范围从[1,n]调到[2,n-1],定义kk为2倍并使用位运算,各种特判+return 0可以获得最少40,最多75

还有clock的,魔法常数的...

教练给的是二分套二分枚举长宽,应该是n2log2n

但其实这也是个半玄学,因为对于同一大小的矩形,不同位置的非法情况也不一定相同,和要么小于k,要么大于2k,左右界和mid不好定

还有单调栈+悬线法做的

悬线法就是在某些格子不能选的情况下,处理出每一列的“悬线”,就是最高/低能到哪里,因此每根线都有一个初始位置和高度,接下来就用这根线左右扩展,看能扫出多大的矩形,由于肯定以低的线为界,要用单调栈维护

那么这道题就是先找到一个和2k的矩形,然后检查每一行,如果一整行的和2k就要一格一格删,删完把这一行作为答案即可,否则直接删一整行,直到找到一个极大子矩形符合条件

n2

code

T3 数组

大数据结构题

根据计算公式

φ(n)=n×piαi

可以去分别维护前面的区间乘积和后面的质因数种类

看到ai,x都不超过300,所以对他们都进行质因数分解也顶多用到62个,那么就可以用一个ll状压存储有没有第i个质数

然后就是线段树time

常数有点大

T4

啊

核心就是一个n的暴跳(专业的叫根号分治)

在树上跳k次可以使用长链剖分的k级祖先,长链剖分就是把子树深度最大的点当重儿子

板子

预处理sumi,j表示从i开始每j级祖先选一个点,一直选到根节点 的总和

那么就设置一个阈值为n,如果cin,就用sum搞一个类似前缀和的东西,把公共的选的部分减掉,但由于左右点到lca可能不一样,所以重合部分要分开算分别减去

否则暴跳就完了,反正不超过n

要注意的是lca可能算两遍,要特判

code

2024.7.13

T1 炒币

贪心,找单调区间,峰点买入低谷卖出,都是在区间交界处

code

dp也能搞,但太菜了

T2 凑数

先按性价比(x,ya,zb)排序,算先枚举值小的

假设优先级是A,B,1,那么A最多用nA次,B最多用A次(否则可以将一部分B换成A,更优),那么A,nA一定有一个小于n,那么挑小的枚举即可

注意可以选0

code

O(n)dp应有30但RE了就。。。

T3 同构

猜结论或打表

比较好猜的是

  • 所含质因子种类相同的数字可以互相换

也好解释,互不互质就看的是质因子

但下面这个就不好猜了

  • 所有满足nx相同的质数x可以互相换

粗略的理解就是有些质数可以和1,2,...,nx相乘从而形成一条链,比如n=14时的5,10

那么如果能交换,很明显链的大小要一样,那么就是nx相同

还有一个就是

  • 1>n2的数可以互换

用上面建图方式理解(不互质的连一条边),那么这些数肯定都是单点,互换肯定可以

code

T4 最近公共祖先

考虑黑点的贡献

一个黑点肯定会对自己的子树提供一个备选

再根据询问可知:黑点的祖先 会为 其子树中不含黑点部分 的点 提供一个备选(以该黑点为跳板即可)

那么每更新一个黑点,就可以不断跳父亲来更新对应部分的点的权值

可以dfs序+线段树维护

但是树的深度未知,跳父亲直到根节点就可能会很慢

事实上,每个点作为父亲产生贡献只需要一次,因为权值是固定的,所以跳到一个已经用过的父亲,就可以停了

但是注意的是,应当先更新完再停,因为即使父亲相同,黑点不同,要更新的子树也不同

code

2024.7.14

爆的最爽的一集

T1 道路

假设原图中两点距离为 dis,那么操作会:

  • dis为偶数:dis=dis2

  • dis为奇数:dis=dis+12

可得当距离为奇数时,会多一个1

那么就统计有多少对点的距离为奇数,然后加到原图中任两点距离上,最后除以二即可

那么怎么求距离为奇数点对数呢?

观察dis的计算:

dis=depu+depv2×deplca

减数是个偶数,那么加数一奇一偶即可

就是深度为奇数点×深度为偶数点

完了

附:原图中任两点距离计算(考虑边的贡献):

oridis=utreesizu(nsizu)

code

T2 集合

最大化异或和的经典做法是01trie,然后考虑满足前两个条件

对于条件1,可以记录树上每个节点中经过该节点的最小值,只要满足sx就表明存在可行解

对于条件2,可以得到k|v,那么就把v插到k代表的字典树中作为备选答案(路径),到时候在k的树上走的时候走到他就是他

又因为不知道k,所以预处理每个数的所有因数(不只是质因数),到时候就把新加的x插进其因子代表的字典树中

这里注意进入一个节点的条件:

  • 经过该点的最小值sx

  • 该点有编号(存在)

走到中间不行了立刻输出-1

code

T3 科目五

没开ll爆了70

二分应该是伪的,但考场上(下)有人能过

问了一下发现check里二分一次加满油后最远能到的城市可以卡过去(但能优化多少取决于起终点距离,距离小的话几乎没啥用)

后来发现二分能过,但是要随机化加一个log,那就不会了

code

正解dp

定义dpi,j,k表示从城市i走到城市j,加k次油的最大油桶,这里先不考虑ci,我们总不能对每次行程都dp,也没必要

那么方程就是

dpi,j,k={ajaik=0minc=i+1jmax(dpi,c,k1,ajac)k>0

朴素版本是O(n4)

接下来用一些奇技淫巧可以发现每次的转移点c是单调的

那就可以仿照单调队列维护转移点即可 这叫单调指针罢

tip: 硬开三维400×400×400会爆空间,可以离散化或者滚动k

code

T4 监狱

屌题

如果有两个人的路径重叠了,那么这两个人的移动就要有先后顺序

具体的:

  • A的起点在B的路径上,那么A应当先于B

  • A的终点在B的路径上,那么B应当先于A

不妨A先于B当作AB连边,那么原问题就等价于连的新图中有没有环

然而重叠部分可能不止一个点,那么某点要连边,就要向路径上所有点连边

如果暴力连接,是平方级别的,但是有分

参考

正解是线段树优化建图... 炸弹还是要炸死人了

2024.7.15

T1 传送带

对于当前所在点x:

lj表示x左边第j会让球转向的位置,rj同理

那么一共有四种情况:

  • 开始向左(右),从左(右)出

这里以开始向左,从左端出为例:

dis=xl1+(r1l1)+(r1l2)+(r2l2)+...+(rvlv)=2×(k=1vrkk=1vlk)+x

其他情况照着推即可,比如右始左出时x要减去,从右出时最后一项应当是n+1lv

接下来考虑v怎么得到

根据样例可得:如果序列中有le<,那么[1,le]从左出,剩下的点从右出

code

T2 math

点击这个再点击目录里的裴蜀定理发现推广2和题目中的形式几乎一样,没取膜而已

那么根据取模尿性,可以猜测

gcd(aimodk)|x

但试验后不对

根据取余原始含义可以写出:

pk+x=aibi

其中p是整数

然后变一下

x=aibi+(p)k

再用一遍裴蜀定理可得到

gcd(k,aibi)|x

再根据推广:

gcd(ai)|aibi

所以

gcd(k,ai)|x

那么答案就是[0,k1]gcd的倍数

code

T3 biology

fi,j表示走到(i,j)的答案,那么

fi,j=max(fi,j+|ii|+|jj|)+bi,j

肯定爆

然后考虑到ai,j要单增,再结合题目就没说路径必须是格子挨格子,所以直接开结构体对所有ai,j0的格子按a排序

那么对于ai,j相同的,就是选一个,最终组成一条路径

那么我们可以对每一条a相同的“线段”做一个dp,那么只可能从上一条线段转移而来

对于一条线段,结合原始dp,拆掉绝对值,可以分成四部分

  • dp+ij,dp+i+j,dpi+j,dpij

开四个变量维护即可

注意的是,在更新dp时,四个变量必须和当前点i,j的四种组合一一对应

ll p = h[1] - c[i].x - c[i].y;
ll q = h[2] - c[i].x + c[i].y;
ll r = h[3] + c[i].x - c[i].y;
ll s = h[4] + c[i].x + c[i].y;

code

T4 english

不会

2024.7.24

从此开始难度随机排列

T4 Black and Black

构造

先令bi=i,然后计算S=aibi,如果刚好S=0就完了,否则调整

此时已经满足了单调性,为了维护这个东西,最稳妥的方法就是在1i 整体减去一个数,或者在jn整体加一个数

如果S>0,说明要减去一个S,那就是按照上述做法在开头或结尾一部分操作,要在开头减去,需满足ai的某个前缀和是1,这样整体减一个S即可。要在结尾加,类似的需满足后缀为1

S<0同理

code

T3 White and Black

如果一个节点是黑的,那么就要统计儿子中是白色的个数,因为这些儿子要变回白色还各需一次操作

那么转化一下就可以变成:儿子数 - 儿子中黑色的个数,因为黑色的儿子肯定在给出的集合中,方便操作

再变一下就是:如果该黑点的父亲是黑色,就省了一次操作

code

T2 White and White

dp

最裸的就是定义dpi,j表示把前i个数分成j段的最小和,则dpi,j=min(dpk,j1+(sumisumk)%p)

然后考虑优化

根据取模性质可以得到:dpi,...sumi(modp)

对于dpi,j的两个转移点x,y,有dpx,j1+(sumisumx)%psumx+(sumisumx)%psumi(modp)y同理

所以dpx,j1+sumxi%pdpy,j1+sumyi%p(modp)

又因为sumxi%p,sumyi%p都小于p,所以dp越小,和越小

所以对于每个i,j,记录转移位置posi,j,对于每个j,用i更新转移位置

为了压缩空间pos可以滚动

code

T1 Black and White

四个正解一个不会

60分做法:预处理出两点距离,最大两点距离和对应的两点,如果修改不影响记录的两点,不管,如果把一个改白了,就在剩的点中重新找最大距离。如果是把一个点改黑了,就把他和原先的黑点配对找最大距离并加入黑点集

code

非魔法能看懂的正解:

维护括号序列,原理等见这里

code

2024.7.25

T1 Permutations & Primes

yy构造题

MEX就是区间内没出现的最小数字

首先,要防止1搅局,所以放到中间,这样包含1的区间最多

再把较小的质数往两边放,这样能让不含小质数的区间尽量多

再把剩下的合数扔进去,无所谓顺序

code

T2 树上游戏

k=n1时答案就是1,链的情况下就是均分

正解二分不难想到,重点是check

首先一般情况下check都用贪心,就是走了mid步就放一个,问题就在怎么走

考试的时候口胡了从根走的check,萎了

正解是每次从最深的点开始,走mid步后放一个,然后删掉被这个点覆盖的叶子节点,重复这个过程。正确性还算好理解

不太会删点,所以换个方法,本质类似:

fi表示放的点中到i的最小距离,gi表示从i开始最远能走多远(就是i到最深点)

那么放点就相当于gi=mid,删点就相当于fi+gimid,就是存在一个放好的点能一口气盖到最深点

最后要特判根节点

code

T3 Ball Collector

软肋题(并查集)

考虑给每个路径上点的a,b连边,那么就变成了从若干连通块中取完所有边,每个边配一个点,求最多能取多少点

画一下图就发现除了树只能拿出n1个点外,其它类型(边数)的连通块都可以取出所有点

又因为n1恰好是树的边数,所以每个块的贡献还可以写成min(V,E)V,E对应点数,边数

所以可以使用并查集维护连通块,又因为终点一堆,所以要在回溯时删掉离开的点,就是可撤销并查集

更多细节见码

T4 P7028 [NWRRC2017] Joker

屌题

定义pi,qi为前i个正数/负数的和,那么(下列所有式子未经说明字母均代表绝对值)

si=piPqiQ

然后就有了两种做法(但本质类似)

  • 1.

如果i<j并且要si<sj,将上述化简式子带入按照有点熟悉的形式可得

qiqjpipj>QP

我超,斜率+凸包

那么答案查询就是二分斜率找点

接下来考虑修改

发现当修改位置为i时,对于某些j,只是给分子或分母的两个pq同时减去一个数,再加上一个数,抵消了,不影响斜率,但值变了,这就是平移

所以对序列分块,每个块建一个包,修改点所在包重构,其余平移即可维护

  • 2.

进一步化简si可得:

si=1PQ×(piQqiP)

没修改的时候就是要最大化后面那坨,然后发现这东西可以使用第三维是0向量叉积(?????)

然后还是凸包,还是分块维护

...

参考

2024.7.26

T1 Not Sitting

Tina的选择明显是在四个角,所以计算一下每个格子到四个角的最大距离,排序即可

考场上写的宽搜,就是Rahul肯定是在中间落座,然后逐层向外,典型宽搜

考试时不小心把v打成1了...挂了40

T3 初生的东曦,彼阳的晚意

就black and white那天的题加了个区间查询,这次用了线段树维护直径,因为直径有个性质:合并后直径的端点只可能来自合并前两棵树各自直径的四个端点,证明略,而且这个还比括号序列好理解,好理解就相对好写

但标题是为什么呐

嗯...

code

T2 s

可以组合,可以dp

教练给的dp法没看懂(doge)

组合法原理见这里

code

T4 Everything on It

科技,跳了

大概解释一下:

反演那部分不说了,套的板子,原理还行

重点看fi的求法

首先明确fi表示选出(钦定)i个调料出现次数小于等于1,那么首先就是一个(ni),然后分类

  • 钦定部分

这部分可以分为两类:出现一次,没出现

没出现不管,对于出现一次的,设有j个,此时共(ij)种,让这些调料出现在k碗面中,方案是第二类斯特灵数,所以单个j答案是(ij){jk},再根据j的范围,可得答案为j=0i(ij){jk}

但是那k碗面里还有别的料,这些就随便了,剩了ni种料,一碗加不加共2ni种,每碗之间相互独立,所以共2k种组合

再枚举一下k

三者相乘得到第一部分

k=0i2(ni)kj=0i(ij){jk}

  • 未钦定部分

这部分简单,加不加2ni种,每个加的方法出不出现是二的次方级别,为22ni

然后两部分以及开头的组合乘起来,就是fi

2024.7.27

萎了

T3 组合

看到Catalan,忘了咋求还对应错了情景(悲)

  • typ=1

就是每时每刻向右走次数不少于向左走次数,裸的Catalan,然后因为要走回去所以除以2

ans=H(n2)

把这对到情景0去了...

  • typ=3

此时横纵方向都是Catalan,枚举横向走了多少,然后选出横着走的,再乘上两个方向上的Catalan即可

ans=i=0,i+=2nCniH(i2)H(ni2)

  • typ=0

这部分没限制,和卡特兰没关系,同样枚举横向走了几步,然后从横、纵步数中各选出一半向某方向走,剩下的就往相反方向走

ans=i=0,i+=2nCniCii2Cnini2

  • typ=2

这里比较复杂

dpi表示走了i步后回到原点,那么枚举中间未经过原点的步数,这些步数只能往一个方向走还不经过原点,是卡特兰,然后又因为四个方向,要×4

dpi=j=0i4×dpijH(j21)

答案dpn

code

T2

高消有30,系数就是距离,但是由于系数数组必须用double,所以赋值时要给距离乘一个1.0

接下来看std:

定义sumu表示以u为根的子树的点权和,vu的一个儿子

根据题目可以得到一个式子:

bv=bu+sum12×sumv

原理就是从uv时到v子树内部点(sumv)的距离均会减小1,到外面点(sum1sumv)的距离都会增加1,所以就有这个式子

然后来看看怎么用它

  • ab

b就是sum从叶子到某点的叠加,可以开个数组dfs实现,但是该数组里的只是子树内部sum的叠加,并不是b,但是这样可以求出b1(整棵树都是他的子树。。。),然后按那个式子dfs推下去即可

  • ba

对于所有非根结点都有上述式子,所以可以得到n1个式子,

先变一下形式:

bv=bu+sum12×sumv

然后把n1个式子累加:

i=1nkibi=(n1)sum12×b1

解释:左边,由于加减不同,所以每个ki用来表示系数,但是发现每个式子中父节点(u)的系数为1,子节点(v)的系数为1,可以用此特点dfsk;右边,非根结点的sum叠加就是b1

然后就能求出sum1,再结合最初的式子得到sumu,进而差分得到点权

code

T4 大dp

原理见这里

code

T1 boss

dpi,j表示操作i次后余数为j的概率,转移较简单

dpi,j×kmod=j,k=1mod1dpi1,j×numk

numk表示a数组中模mod余数为k的数的个数

能拿50(复杂度O(mmod2)

看到m109,有点矩阵加速的味道

这里的阻碍就在于num是个1×(mod1)的矩阵,但是快速幂要求矩阵是正方形,难以想到

用矩阵在图上的应用来理解是没有问题的,相当于向mod1个方向游走

借助此将num扩充为正方形,他应该长这样:

[abc..bca..cab........]

mod1000,肯定不能拿完整的矩阵搞,其实,直接按着图的套路走就行了,开一个备份数组,每次将num备份后在num上跑一个mod2更新就行了,复杂度O(logmmod2)

code

2024.7.29

T1 花间叔祖

余数种类最小肯定同余的多,再结合同余条件是差为模数倍数,很容易想到对差分数组求一遍gcd,如果gcd不为1就直接输出1(膜gcd),否则直接输出2(膜2

没想到2能兜else的底,挂了25...

code

T2 合并r

想到dp,状态都出来了,但不会转移

定义dpi,j表示前i个数和为j的方案数

考场上只想到添数法转移,然后就不会搞添12k的转移,感觉下标都成小数了,就把dp舍了

后来发现还可以通过给和为2j的序列除以2来得到和为j的序列,那添数只用添1就行了,其他的小数就是之前添的1不断除以2得到的

dpi,j=dpi1,j1+dpi,2j

初始化:dpi,i=1,就是全放1的情况

T3 回收波特

神秘题

一点思路没有,还好暴力给的多点

首先有这么一个结论:

如果两个波特的坐标某时刻是相反数,那么在以后的操作中他们的坐标一直是相反数

证明不难,yy一下就出来了

这就说明,对于对称的部分,我们只维护一半就行了,另一半可以推出来,实现的话可以连边

由于波特太多,有个小trick是平移原点

查询的时候,就相当于跑链直到回至原点/没映射的点,这里可以使用记忆化优化

code

T4 斗篷

读都读不进去...

字符串s

getline(s)?,不对

s.getline()?,不对

cin.getline(s),还不对

(换成char)

getchar(ai,j)?,不对

ai,j=getchar()?,对(能运行)但不行(格式不对)

... (玩集贸跳了)

getline(cin,s)

getline(cin,s)

getline(cin,s)

暴力戳这里,有注释

还有一点没说就是getline会读入r,c那行后面的回车,所以要从0开始

std:

定义Lx,y,Rx,y,prex,y为点(x,y)向左上/右上/左最远能延伸多远

对每个点都可以有下图的关系:

贡献统计可以使用单点加减1的方法,数据结构用树状数组就够了

代码里应该是把这个图逆时针旋转90度看的,反正原理一样

还有一点,就是跑一边只能求出正的三角,还需把图反转再跑一遍

80分TLE代码...

2024.7.30

昨天晚上没关空调睡觉结果感冒了,还一直咳嗽,今天只想睡觉...

T1 XOR Matching 2

枚举x并用它和a得到对应数组,去和b比较一下验证是否合法即可,注意去重

code

T2 S

这个

不同点就是现在求最小次数

同样对最终串dp,形式基本同那道题,前缀和优化都一样,不同的就是代价那一位用来记录末位是哪个字母(0/1/2)

code

T3 Y

屌题

解析戳这里

T4 O

这个

难蚌题,目前知道原理但不会写

2024.7.31

咳嗽,呕了一上午

T1 黑客

直接枚举x,y约去公因数后的数字i,j,满足i+j=z[0,999],然后反推出各自可能的公因数取值范围,交集长度就是i+j出现次数,相乘累加即可

code

T2 密码技术

首先发现行列互不影响,那就是行列方案数乘积

如果a,b两列能换,b,c两列能换,那么其实a,b,c就能自由换(哪怕a,c不能换),这样就用并查集处理各个联通块的大小,答案就是siz!

code

T3 修水管

状压暴力应有45但没改出来

考虑一个大炮

一个水管有漏水可能的前提是前面的水管不是被修了,就是没事儿,考虑从这点出发求解概率

定义fi,j表示r轮结束后i段修了j次,前i段都不会漏水的可能性

对于第i段,根据上面的说法,他要么在后续的修理中一直没坏,概率为(1pi)rj,要么坏掉了消耗一次修理次数,概率1(1pi)rj+1

所以有

fi,j=fi1,j×(1pi)rj+fi1,j1×(1(1pi)rj+1)

接着设gi表示修理第i段的概率,那么前i1段经过若干次修理后不漏水,然后他自己还得坏掉

gi=(j=1rfi1,j)×(1(1pi)rj)

最后ans=gidi

code

T4 货物搬运

之前分块有一道入门是单点插入,好像没做

没啥思维,vector + 分块即可

code

还有平衡树写法,不会

posted @   why?123  阅读(85)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示