25暑假 个人刷题

ABC394E:
题意:有n个顶点的有向图,以邻接矩阵\(C_{ij}\)的形式记录了\(i\)\(j\)的字母“边权”(不存在为-),对于每一对\((i, j)\),需要求最短的,且路径上的字符为回文的字符长度。最终呈现的结果是一个“路径长度矩阵”。

Problem

题解:

首先观察题目可以知道,自己走向自己的最短路程是0(同时也是回文的),所以对角线上元素一定为0。又因为一个字符一定是回文,所以那些从起点可以一步到达的点的答案为1。

考虑类似BFS的做法:将已经得出的答案的点列加入队列,每次抽出一个\((i, j)\),从\(i\)延伸出一个点\(k\),从\(j\)延伸出一个点\(l\),此时判断\((k, l)\)是否为第一次遍历到,以及此时字符串是否为回文,如果以上两个条件均满足,修改答案矩阵,并将新的\((k, l)\)插入队列中。如果搜索完还是没有,那就是没有,填-1输出。

Submission

ABC394F:
题意:查找所给无向图中的最大“烷烃树”(要么连1个要么连4个)

Problem

题解:

由烷烃的性质(在化学里我们是不是只需要看里面碳原子数量就行了?!),得出我们只需要判断其中链接4个点的点即可(剩下的直接套烷烃计算公式\(3n+2\))。
此时,我们把链接小于四个点的点全部抹去,只剩下所有的“核心点”以及连边。此举如同在一棵树上删去一些树枝,并有可能将一颗树拆分成多个树,此时对每个树的操作都是独立的。
对于剩下的图,选出不存在链接大于四个点的点的最大连通块。对于该问题采用树形dp。
\(f(u, 0)\)表示以\(u\)为子树当中,包含\(u\)的最大连通块大小,同时对应的\(f(u, 1)\)表示以\(u\)为子树当中,在\(u\)已知有一个度数要分给\(u\)的父节点的情况下,包含\(u\)的最大连通块大小。显然的,对于\(f(u, 0)\),需要求与其联通的前四个最大子块的大小(这在通过\(dfs\)的树形dp中已经被先行确定),对于\(f(u, 1)\),则需要求与其联通的前三个最大子块大小,并且对为父节点留的一个度数\(+1\)
(此时不需要管父节点又连了多少多少点,因为我们使用的是\(dfs\)从下往上遍历,此时不需要处理父节点,在之后的遍历中会重新处理一遍)
上文中的\(f(u ,0)\)的最大值即为所求的\(n\),然后套烷烃计算公式得到答案。
Submission

CF1026C:
题意:无人机初始高度为0,构造一个“准上升序列”(即只有上升和平飞,没有下降),使无人机在每一个点都满足题意中的合法区间。如果没有合法区间,输出-1。

Problem

题解:

由于无人机只能上升或者下降,我们可以首先不考虑障碍,构造出一个“可能高度区间”序列,并与题意中的合法区间做交集,得到每个点上无人机合法的可能区间,然后通过确定末位置往前逆推即可构造序列。

Submission

ABC410C WA提醒

在计算偏移量时,如果直接用偏移量+原本的时间,会存在如果有该量为\(n\)的倍数的话,最后表示的坐标为0,导致错误。
AC Submission

ABC410D
题意:求最小异或路径

Problem

Tips:看清楚题给条件是有向图,如果是无向图的话做法会复杂的更多,参考CF845G。
题解:

设“权重路径”为一个二维数组\((i, j)\),即在\(i\)处权重\(j\)。在初始情况下位置为1,权重为0。即为从\((1, 0)\)开始\(bfs\),递推方程为:
设此时是从u点到v点,初始值为i,该路径权重为x,\(f[u][i] -> f[v][i^x]\)
对n点的权重路径从小到大遍历(因为后面那个值只在0到1024中间),找到最小的权重值直接输出即可。
Submission

CF1034E(第一次vp出四题,虽然是div3简单场)
题意:找一个给定数组中,精确删除k个数之后,\(MEX(a)\)的可能值个数。

Problem

题解:

将问题从另一个角度看,因为数组的\(MEX\)一定不会大于这个数组的最大值,那么对于该数组中每一个可能出现的\(MEX\)值是存在一个"删除个数"的区间的。那么此时就变成了统计所有的"删除个数"区间。很显然的,这里如果使用普通递增\(O(n)\)的复杂度是超时的,需要利用差分标记端点实现\(O(1)\)修改。

那么我们现在来看这个\(MEX\)区间怎么求。对于区间左端点,显然就是将所求的值在数组中的个数(通俗来说,就是将所求的值从数组中"抹去"),这里可以在输入时利用一个\(count\)数组来实现\(O(1)\)取用。对于右端点,除了要将求左端点时所求的减去,还要减去所有大于该数的元素(因为他们对于\(MEX\)没有意义),对于小于该元素的元素,需要减去所有重复的元素,使他们只剩下1个。对于右端点的这些条件,最后的总数也是易求的,详见代码。

Submission

CF973G(状压DP例题)
题意:要求删去最少的歌曲,使得每一个相连的歌曲都存在至少一个特征相同。

Problem

解释状压DP:

现在存在一个二进制字符串,利用二进制字符串中每一个0/1来表示对应的状态(通常的,每一位会与题目中每一个数/点/....形成对应),例如该题中的"删去/不删某首歌曲",就能通过0/1来表示。将二进制表示的串用十进制解码,就对应了每个状态所对应的值。

PS:状压DP用0-index更好写

题解:

当然,在这里只是需要对应情况存在,那么\(dp\)数组实际上是一个\(bool\)类型数组,代表该种情况是否满足题意。

对样例打表,发现如果我们先选第一个再选第二个的话,那么接下来是没有歌曲能够接在后面的,但是如果先选第二个再选第一个,那么第三第四两个歌曲都可以按照题目规则接在后面。如此我们发现不止与"选取了哪些歌曲"有关,同时也与"选取的歌曲中最后一个是那个"有关,故此时的\(dp\)数组更应再加一个维度:选取的歌曲中末位是谁。

此时\(dp\)数组为:\(dp[所选歌曲状态的二进制表示][末尾歌曲编号]\)

初始条件:\(dp[只选第i首歌的状态表示][i] = true\)

转移方程:若x与y有相同特征,且\(dp[i][x] = true\),那么\(dp[i|(1<<y)][y] = true\),其中\(i\)表示前一阶段状态压缩表示的数,\(1<<y\)表示在第\(y\)位为1其余为0,与\(i\)或运算之后就可以单点修改\(y\)的状态。

答案:若\(dp[i][j] = true\) ,求所有\(i = 1\)数量的最大值,\(ans = n-max\)

另:如果还有不理解的记得看提交中的注释

Submission

CF1276B
题意:计算对一个无向图中,对给定的\(a, b\),有多少点对\((x, y)\)\(x\)\(y\)的路径中必定包含\(a, b\)
题解:

分别从两个给定点开始\(bfs\),当在\(bfs\)中碰到另外一个点就跳过,这样分别得到\(a, b\)单独能到达的点,取补集就能得到"不经过\(a/b\),从\(b/a\)点就不能到达的点",乘法原理相乘即可。

Submission

ABC415E
题意:一个人在\(h\)\(w\)列的图上的\((1, 1)\)点上,在往右下移动的\((h+w-1)\)次中,每次都能在新到达的点上得到金币,并会扣除一定量的金币\(P_{k}\)。求最开始需要最少多少金币,才能在金币一直\(>0\)的情况下行走\(h+w-1\)次。
题解:

首先的难点在于题目中给的条件是\(h*w \leq 2*10^5\),需要我们使用二维\(vector\)动态开大小。

观察题给的\(P\)数组大小\(h+w-1\),亦是从\((1, 1)\)点到\((h, w)\)的折线距离,也就是说这个人的终点一定是\((h,w)\)

很简单的贪心思想启示我们,当满足题意的时候,最后在\((h, w)\)一定不会留下金币,如果我们用一个\(f\)数组表示每个点操作过后所欠的金币,那就是\(f[h][w]=0\)。但是我们现在只知道最后的情况,但是要求的答案是最开始在\((1, 1)\)时的金币,那么从这里就很容易想到了从末状态往初状态递推。

如果想到了上述内容,那么得出如下递推式是很容易的:

设在\((i,j)\)增减的金币为\(n\),那么\(n = p[i+j-1] - a[i][j]\)

在最底边,也就是\(i = h\)时:\(f[i][j] = n + f[i][j+1]\)

在最右边,也就是\(j = w\)时:\(f[i][j] = n + f[i+1][j]\)

在一般位置时,因为我们的要求一定是欠的钱尽量少(不然在初状态还的金币就会多了),故可推\(f[i][j] = n + min(f[i][j+1],f[i+1][j])\)

本以为这样就可以了?

错了!如果是负数怎么办?这样在初状态还要先不拿金币吗?

所以在每次递推时,需要判断\(f[i][j]\)和0的关系,最小必定为0;

全文结束。

附代码:

#include <algorithm>
#include <bits/stdc++.h>
#include <vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define endl '\n'
#define cty cout<<"Yes"<<endl
#define ctn cout<<"No"<<endl
#define int long long 
int h,w;
int p[200010];
void solve(){
    cin>>h>>w;
    vector<vector<int>> a(h+2,vector<int>(w+2)),f(h+2,vector<int>(w+2));
    for(int i = 1;i <= h; i++){
        for(int j = 1;j <= w; j++) cin>>a[i][j];
    }
    for(int i = 1;i <= h+w-1; i++) cin>>p[i];
    for(int i = h;i >= 1; i--){
        for(int j = w;j >= 1; j--){
            int n = p[i+j-1]-a[i][j];
            if(i==h&&j==w) f[i][j] = n;
            else if(i==h) f[i][j] = n+f[i][j+1];
            else if(j==w) f[i][j] = n+f[i+1][j];
            else f[i][j] = n+min(f[i][j+1],f[i+1][j]);
            f[i][j] = max(f[i][j],0ll);
        }
    }
    cout<<f[1][1];
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int t = 1;
	//cin>>t;

	while(t--){
		solve();
	}
}
洛谷P2458保安站岗
题意:有一个树状图,在某些节点上需要放置保安,保安能看到其所在节点和其相邻节点,要求所有节点都可以被看到,求花费最小代价。
题解:

经典树状\(dp\),但因为忘记考虑一种情况,10分成功坠机(大悲)

\(dp\)数组开二维,第二个维度用来表示:

1.该点上有保安

2.该点的父亲上有保安

3.该点的儿子上有保安

显然可以看出来的是,每个叶子节点\(dp[i][1]\)=\(w[i]\),然后按照正常树状\(dp\)思路即可。

Submission

ABC414E
题意:在给定的\([3, N]\)范围内找到满足\(a\)%\(b\)=\(c\)\([a, b, c]\)互不相等三元组个数。
题解:

在做这题的前几个小时被集训队内大佬安利做这题,此题是数论分块裸题,对数论分块有着启蒙意义。

首先,根据题给的互不相等的性质,很容易推出\(a>b>c\)这个性质,同时\(c\)作为余数就注定了其作为副产物的命运,接下来固定\(b\),我们来看\(a\)的范围。根据最先推的性质可以知道,\(a = q * b + c , q>1\),当\(c = 0\)时,易得\(a = q * b\)\(q\)的取值范围为\([1, (int)N/b]\),同时这也对应了\(a\)\(b\)倍数的取值范围。

如果我们不考虑一定为倍数,那么根据一开始得到的范围很容易得到取值范围为\([b+1, N]\)

根据如上范围,我们可以得出合法的\(a\)的范围为:

\((N-b+1)-(int)N/b\)

对于每一个\(b\),求和、取模即可。

但是!这题给的范围在如上描述下的\(O(n)\)复杂度下会超时!

观察得到:如上的式子,前半部分可以通过等差数列的方式求解,高耗时部分主要在后半部分整除后相加。

这里就需要利用整除分块的思想了:其核心在于,观察到\((int)N/b\)在某段区间内是相同的,就可以按照其值(此处假设为\(k\))对整段区间进行分块,这里分出的是"所有的\((int)N/b = k\)"的区间,就可以通过公式相加,避免了循环的高耗时。

下面给出一种计算分块区间边界的方法:

假设当前分块起点为\(left\),设当前\((int)N/left = k\),需要找的下一个区间起点要求是:\((int)N/L < k\),且\(L\)尽可能小。又由\((int)N/L < k\)可得\(N < k*L\)\(L > (int)N/k\),可得\(L = (int)N/k+1\)。同时需要保证区间尾仍然在合法范围内,所以需要和\(N\)\(min\)

通过显然的求和公式,可以得出在该区间内的和为\(k*(L-left)\),累加,同时别忘了将\(L\)赋值到\(left\)上!

Submission

校内挑战赛2H
题意:求\(\sum_{i=1}^{n}k mod i\)
题解:

通过一个在高精度转换里用到的技巧:\(kmodi = k - i* \lfloor k/i\rfloor\),在求和的时候,前半部分直接拿公式套就行,后半部分用本篇题解中\(ABC414\)中数论分块的方法解决,接近裸题但是我赛时没写出来┭┮﹏┭┮

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define cty cout<<"Yes"<<endl
#define ctn cout<<"No"<<endl
#define dbg(x) cout<<"x:"<<x<<endl
#define int long long
const ll mod = 998244353;
const int mxn = 2e5+10;

void LonelyLunar_solve(){
	int n,k;
	cin>>n>>k;
	ll ans = 0;
	if(n>=k){
		ans+=(n-k)*k,n = k-1;
	}
	ans+=(n*k);
	//cout<<ans<<endl;
	int left = 1;

	while(left<=n){
		int t = k/left, L = min(n,k/t);
		int len = L-left+1;

		ans-=1ll*t*len*(left+L)/2;
		left = L+1;
	}
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int _ = 1;
	//cin>>_;
	while(_--){
		LonelyLunar_solve();
	}
	return 0;
}
校内挑战赛2G(洛谷P1896)
题意:在\(n*n\)内放\(k\)个耄耋,要求每个耄耋周围八个点内不能有别的耄耋,求放置方法。(神人题面)
题解:

一道相对比较直白的状压\(dp\)(特别是看到\(n \leq 9\)的题面之后很容易往状态压缩那个方向想,所以为什么我想到的就是爆搜呢),通过0/1表示该点是否合法放置(0表示可以放置),状压\(dp\)的思路主要在于不断枚举表示状态的数,不断转移。

按照状压\(dp\)的思路,我们设\(dp[i][j][S]\)表示我们选到了第\(i\)行,第\(i\)行的状态为\(S\)(因为全是0/1,我们可以通过二进制转换成十进制,把状态用一个数表示出来),现在已经选了\(j\)个耄耋的合法状态数。对于每个状态\(S\),我们在选接下来要选的国王时,需要判断该状态(也就是最终展示出来的数)是否合法。对于新一行,很显然的是需要和上一行选择过耄耋的位置错开。通过位运算的相关技巧,比如左移右移,与运算等,可以正确输出。同时,在\(n\)行选择结束后,因为不知道最后一行的状态里哪个所含的合法状态最多,所以,需要遍历最后一行状态才能找到最大解。

Submission

CF edu181 D
题意:现有一个分为\(m\)个单元格的条带,每个线段以概率\(p/q\)独立存在。要求计算每个单元格恰好被一个线段覆盖的概率。
题解:

整个概率计算可以分为两个独立部分:

\[总概率 = (覆盖路径的概率)*(其他线段不存在的概率) \]

我们设\(dp[i]\)为覆盖到位置\(i\)的路径概率,当线段\((l, r]\)被选中时,可以拓展到\(r\)。考虑一个线段的存在概率是\(s/d\),如果考虑其加入路径,需要满足的两个条件是:1.该线段必须存在,概率是\(s/d\)。2.该线段在路径中没有被其他线段覆盖,概率是\(1/(1-s/d)\)。两者相乘,可得总概率为\(s/(d-s)\)

Submission

ABC415F
题意:给定一个字符串,并且需要支持在线单点修改和在指定的\([l, r]\)内查询最长连续子串。
题解:

一道比较经典的线段树题,我决定把这道题当作我的线段树启蒙题。

题解在此不再解释"为什么想到线段树"。其实你打一遍暴力喜提TLE之后就会想到了

根据题目的要求,我们建树的时候就要考虑到两种情况:1.连续子串在一个块内2.连续子串跨块。针对前者很好维护,针对后者,需要在回溯的时候判断两者的头尾是否相等(也就是最长子串有可能连起来),从而很自然的需要维护块内"左侧最长连续子串的长度,右侧最长连续子串的长度",并与"区间内最长连续字串的长度"。同时需要维护一整块都是一种字母的情况(这种时候直接判断左右首末字母和相邻区间是否联通即可)。

Submission

洛谷P3869 宝藏
题意:可交互式迷宫(踩到机关会改变其对应点的可通过性),判断从起点到达重点的连通性和最短路。
题解:

新学习到状态压缩BFS。观察到数据规模较小,且机关的数量只有10个,故可以通过二进制压缩来表示每个机关的状态。当BFS走到一个机关时,通过异或改变对应机关的状态。如果走到了一个被触发的机关,就通过当前的二进制值来判断是否被触发(需要以此来判断可通过性)。这个二进制状态被放在普通BFS中的\(vis\)数组中,且在每次循环中都需要被遍历。

#include <bits/stdc++.h>

using namespace std;
//typedef long long ll;
#define endl '\n'
#define cty cout<<"Yes"<<endl
#define ctn cout<<"No"<<endl
#define dbg(x) cout<<<<#x<<" : "<<x<<endl
#define int long long
//const ll mod = 1e8;
const int mxn = 4e6+10;
char a[35][35];
int bex,bey,enx,eny;
//状压BFS
struct node{
	int x,y;
	int step;
	int st;//二进制状态
};
int xt[4] = {0,1,0,-1};
int yt[4] = {1,0,-1,0};
bool test[35][35][1<<11];
int ll[15],rr[15],LL[15],RR[15];
void LonelyLunar_solve(){
	memset(test, 0, sizeof(test));
	int r,c;
	cin>>r>>c;
	for(int i = 1;i <= r; i++){
		for(int j = 1;j <= c; j++){
			cin>>a[i][j];
			if(a[i][j]=='S') bex = i,bey = j;
			if(a[i][j]=='T') enx = i,eny = j;
		}
	}
	int k;
	cin>>k;
	for(int i = 1;i <= k; i++){
		cin>>ll[i]>>rr[i]>>LL[i]>>RR[i];
	}
	queue<node> pq;
	pq.push({bex,bey,0,0});
	test[bex][bey][0] = 1;
	while(!pq.empty()){
		auto t = pq.front();
		pq.pop();
		int tx = t.x,ty = t.y,tstep = t.step,tst = t.st;
		if(tx==enx&&ty==eny){
			cout<<tstep<<endl;
			return;
		}
		for(int i = 0;i < 4; i++){
			int xx = tx+xt[i],yy = ty+yt[i];
			if(xx>=1&&xx<=r&&yy>=1&&yy<=c){
				int tes;
				if(a[xx][yy]=='#') tes = 0;
				else tes = 1;		
				//0不可通过,1可通过
				int tss = tst;//这里需要复制一遍状态来判断,不然下面的if的先后顺序会污染状态导致错误
				for(int j = 1;j <= k; j++){
					if(xx==ll[j]&&yy==rr[j]) tss ^= (1<<(j-1));
					if(xx==LL[j]&&yy==RR[j]){
						if((tst>>(j-1))&1) tes^=1;
					}
				}
				if(tes&&!test[xx][yy][tss]){
					test[xx][yy][tss] = 1;
					pq.push({xx,yy,tstep+1,tss});
				}
			}
		}
	}
	//cout<<"-1"<<endl;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int _ = 1;
	//cin>>_;
	while(_--){
		LonelyLunar_solve();
	}
	return 0;
}

CF1032E
题意:在\([l, r]\)内找到\(x\),使得\(f(l, x)+f(x, r)\)最小,其中\(f(i, j)\)\(i\)\(j\)十进制表示中数字相同的位置数。
题解:

纯思维结论题。

从大位往小位看,如果相同的话那么无法更改,区间内任意一个数,在两边的位数都会被取到,所以对最终结果的贡献是2。如果相差1的话,区间内的数只能在其中一个中选,对最终结果贡献是1。当相差大于2的话,可以选中间的数来使得最终结果变小,且之后可以选任意数(因为是从大到小取位,区间会很大,中间任意选数可以避开所有的相同位),之后不会再有任何贡献,所以终止。

Submissions

ABC408E
题意:求1到n最小或路径
题解:

根据贪心从高到低拆位,屏蔽掉当前位为1的路径再去判断联通性,如果屏蔽掉后仍能联通就全部删去,反之则保留。

Submissions

CF920F
题意:给定一个长度为\(n\)的数组,每次查询查从第\(s\)个元素开始,步长为\(d\)\(k\)个元素,并将第\(i\)个元素乘以\(i\)的和。
题解:

按照题解来看是标准的根号分治?又是学习的一题。

首先设步长\(m\),以\(sqrt(n)\)为分界,在分界以上的直接暴力相加,因为一定不超过\(n/d\)个数参与运算,时间复杂度\(O(nk) \leq O(nsqrt(n))\)

对较小的数进行后缀和处理,又由于里面带个乘号所以后缀和的时候要预处理两层。

Submission

ABC393E
题意:从\(A\)中选出包含\(A_{i}\)\(K\)个元素时,求这些元素的最大公约数可能达到的最大值
题解:

名副其实的"枚举因子"。

\(f[x]\)\(x\)\(a\)\(f[x]\)个数的因子,\(mx\)\(A\)中最大值,然后从大到小枚举因子,当某个因子的数\(\geq k\)时,那么从\(k\)开始向上乘到\(mx\),这其上的每一个数,他们选\(k\)个数的最大因子都是这个因子,由于是从大到小枚举,所以每个地方只需更新一次。
备注:类似题目洛谷P1414,且数据范围较小

Submission

CF1043C2
题意:西瓜一次只能买\(3^{x}\),价格为\(3^{x+1}+x*3^{x-1}\),要求在有限步数内找到最优购买方案。
题解:

将西瓜数量转化成一个三进制数,每一位数就对应了在对应幂次下的购买次数(如112,就代表了在\(3^{0}\)购买两次,\(3^{1}\)下购买一次,\(3^{2}\)购买一次。注意,此时不是最优方案)。那么此时我们对原式转换,设:

\[f(x) = 3^{x+1}+x \dot 3^{x-1} \]

对相邻两式做变形可得:

\[f(x) = 3 \dot f(x-1) + 3^{x-1} \]

此时以通俗的语言描述,就是某种较大购买量的"一次购买",可以转化成三次较原来小一个幂次的"一次购买",并且可以减少\(3^{x-1}\)的支出。因此我们便可以从最高位向下枚举,每次以"剩余最多可用次数/2"(因为一次减钱对应的是多两次购买)和"当前最高位"取\(min\),直到可使用次数\(\leq 1\)或者原数只剩下一位(此时代表的是\(3^{0}\),代表最基础的购买,无法再减钱),此时将数位和对应的开支做和,即为答案。

AC submission

ABC420E
题意:每次增加一个无向边或者更改一个点的颜色(白更改黑或者黑更改白),查询点是否能和染黑色的点联通
题解:

就是一个很经典的并查集,下面我解释一下为什么不直接使用我代码中的\(siz\)数组修改而是要另开一个\(co\)数组(代表每个点的颜色):因为\(vis\)数组存储的是整个连通分量中黑顶点的总和,而不是每个顶点的个体状态。当执行类型2查询(切换顶点颜色)时,需要知道该顶点当前的颜色,以决定是增加还是减少连通分量的黑顶点计数。如果没有\(co\)数组,无法获取单个顶点的颜色信息,因此无法正确更新\(co\)数组。

AC Submission WA submission

posted @ 2025-07-03 17:21  AboveFrost  阅读(27)  评论(0)    收藏  举报