22国庆集训笔记

Day -1

没要到假期作业/ll/ll/ll

只写了当天的作业。

感觉要寄。

Day 0

早上现跑到学校里要作业。结果还被保安撵回去写了假条

然后就是漫长的乘车之旅,从 \(8:30\) 一直到 \(16:00\)

发现车上的人比我卷多了,个个都通宵写作业/kk

电脑在箱子里,不能写代码,有点难受。最后还是向 hjr 借了个平板。

晚上在酒店写了点作业。

Day 1

以后有比赛就直接写题解。

补题链接

T1 - 排序问题

签到题。

通过观察样例,发现只需要将两个数组一大一小排序计算即可。

T2 - 游戏

先说结论:在 \([1,n]\) 范围内,最后只有 \(\gcd(a,b)\) 及其倍数会被取走。

考虑证明:

\(\gcd(a,b)=d\).

首先,我们把 \(a\)\(b\) 表示为 \(x_1d\)\(x_2d\) 的形式,然后就可以发现,无论如何进行加减变换,\(d\) 都会保留下来。

接下来我们来证明为什么 \([1,n]\) 范围以内的 \(d\) 的倍数为什么都会被取到:

同样,我们令 \(a=x_1d,b=x_2d\)。显然 \(\gcd(x_1,x_2)=1\)

然后根据更相减损术,我们可以知道系数最后一定会被消到 \(1\)。又因为 \(1\) 是所有数的约数,所以 \([1,n]\) 范围以内的 \(d\) 的倍数都会被取到。

知道了这个性质,我们只需要判断 \(n/d\) 的奇偶性就行了( \(2\) 是偶数,不影响奇偶性)。

T3 - 农场

平面几何问题转数轴上问题。

发现当前庄稼能被覆盖住当且仅当稻草人离它的距离 \(\le r\)

我们可以发现这刚好构成了一个斜边长度为 \(r\),其中一条直角边长度为 \(y_i\) 直角三角形。

根据勾股定理,另一条直角边的长度 \(k=\sqrt{r^2-y_i^2}\).

我们知道,负数没有平方根,因此当 \(r^2 < y_i^2\),即 \(r<y_i\) 时无解。

这时我们发现,能够覆盖当前稻草人的有效区间为 \([x-k,x+k]\)

此时的问题就转换成了一个区间问题:求最少的点的数量,使得每个区间内都至少有一个点。对于这种题,我们只需要将右端点从小到大排序然后贪心即可。

贪心证明:若将稻草人放在原定位置的右侧,则会有区间不会被覆盖到,无解;若放在左侧,由于其余区间都在右侧,所以不会更优。最后归纳一下即可。

T4 - 最大子序列

这里提供两种做法。

一、DP

\(dp_i\) 表示 \([1,i]\) 区间内最大子段和,我们发现此时有三种情况:一种是 \(a_i\) 不成为最大子段和的一部分,另一种是成为最大子段和;最后,如果前两种情况都不优,就选择空段。这时我们需要开一个辅助数组 \(g\) 表示 以 \(a_i\) 结尾的最大子段和。对于 \(g\) 数组,显然有:

\[g_i=\max(0,g_{i-1})+a_i \]

其中,\(0\) 的含义是指 \(a_i\) 新开一段。显然,如果上一段为负数,那么将 \(a_i\) 接在后面还不如新开一段。

那么,最终的转移方程就有了:

\[dp_i=\max(g_i,0,dp_{i-1}) \]

考虑到这个题需要求两段,那么我们正反各做一次最大子段和,最后枚举分界线统计即可。

二、贪心调整法

具体来说是贪心 \(+\) DP。

仍然用上述方法求出最大子段和,但是我们求出最大子段和之后将最大子段和的区间全部取反,以抵消贡献,然后重新做一遍最大子段和即可。

考虑如何求出最大子段和的范围:

设该区间为 \([l,r]\),那么 \(r\) 就是从 \(n\)\(1\) 第一个 \(dp_i\not=dp_{i-1}\)\(i\),而 \(l\) 就是从 \(r\)\(1\) 第一个 \(g_{i-1}\le 0\)\(i\)

证明:我也不会。

代码

Day 2

貌似回去要隔离七天。

七天之后还是周末。

作业不用写了。

但 whk 估计也寄了/kk

T1 - 股票交易

实际上就是求逆序对。

可以使用归并排序或者树状数组,但是看到这 \(n\le 100\) 的数据范围,直接两重循环就行了。

T2 - 希望小学

考虑二分。

二分能修的小学数量,显然下界为 \(0\),上界为 \(7.5\times 10^8\)

然后是 check 函数的实现:显然,临时工的数量既不能超过二分的数量 \(x\),也不能超过总共的数量 \(m\)

然后就做完了。

T3 - 子序列问题

依次考虑 \(B\) 的每一位选不选,容易发现这可以表示为一个二进制数,直接状压枚举子集。

我们提取出二进制数的每一位,存到一个新的字符数组 \(C\) 里,然后判断是否回文以及是否在 \(A\) 中出现即可。

回文判断 \(C_i\)\(C_{len-i+1}\) 是否相等,是否出现用指针。

实际上这个做法比较暴力,但是 \(B\) 的长度较小,因此可以通过。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,m,tot,ans;
int cnt[N][35];
char a[N],b[25],c[25];
inline bool is_hw(int pos){
	rep(i,1,pos/2,1) if(c[i]!=c[pos-i+1])return 0;
	return 1;
}
inline bool is_zx(int n,int t){
	int j=1;
	for(int i=1;i<=n&&j<=t;++i) if(a[i]==c[j])j++;
	return j>t;
}
signed main(){
	scanf("%s%s",a+1,b+1);
	n=strlen(a+1),m=strlen(b+1);
	rep(S,0,(1<<m)-1,1){
		tot=0;
		rep(i,0,m-1,1) if((S>>i)&1)c[++tot]=b[i+1];
		if(is_hw(tot)&&is_zx(n,tot))ans=max(ans,tot);
	}
	println(ans);
	return 0;
}

此时我们发现上述算法的瓶颈在于判断是否在 \(A\) 中出现,一位一位枚举,太慢了。

我们记 \(cnt_{i,j}\) 表示从 \(A\)\(i\) 位置往后,\(j\) 字母第一次出现的位置。

显然这玩意递推一遍就能处理:

\[cnt_{i,j}=i[A_i==j] \]

否则

\[cnt_{i,j}=cnt_{i+1,j} \]

这样每次我们就不用暴力匹配了,直接跳向 \(cnt_{i,C_j}\) 即可。

最终版代码:

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,m,tot,ans;
int cnt[N][35];
char a[N],b[25],c[25];
inline bool is_hw(int pos){
	rep(i,1,pos/2,1) if(c[i]!=c[pos-i+1])return 0;
	return 1;
}
inline bool is_zx(int n,int t){
	int i=1,j=1;
	while(j<=t&&i<=n){
		if(cnt[i][c[j]-'a']>n)return 0;
		i=cnt[i][c[j]-'a']+1,j++;
	}
	return 1;
}
signed main(){
	scanf("%s%s",a+1,b+1);
	n=strlen(a+1),m=strlen(b+1);
	rep(i,0,25,1) cnt[n+1][i]=n+1;
	per(i,n,1,1) rep(j,0,25,1){
		if(j+'a'==a[i])cnt[i][j]=i;
		else cnt[i][j]=cnt[i+1][j];
	}
	rep(S,0,(1<<m)-1,1){
		tot=0;
		rep(i,0,m-1,1) if((S>>i)&1)c[++tot]=b[i+1];
		if(is_hw(tot)&&is_zx(n,tot))ans=max(ans,tot);
	}
	println(ans);
	return 0;
}

T4 - 田忌赛马

贪心题。

评价:这道题最大的难点就在于猜想策略。

首先我们将两个序列排序,然后取 \(a,b\) 序列的最大值。如果 \(\max{a_i}>\max{b_i}\),那么就干掉这个威胁最大的敌人,田忌 \(+2\) 分;否则,如果 \(\min{a_i}>\min{b_i}\),就干掉这个 \(b_i\),防止他与己方的大佬同归于尽,田忌 \(+2\) 分;最后,我们用 \(\min{a_i}\) 抵消掉 \(\max{b_i}\),用 \(a_i\) 的牺牲换取大佬的存活,齐王 \(+2\) 分。用指针模拟即可。

如果孙膑叛变,那他就会想尽办法降低田忌的得分。实际上这等价于想办法提高齐王的得分,那么反着做一遍就行了。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define int long long
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,a[N],b[N],ans1,ans2;
inline void solve(int aa[],int bb[],int &sum1,int &sum2){
	int step=n;
	sort(aa+1,aa+n+1);
	sort(bb+1,bb+n+1);
	int min_a=1,max_a=n,min_b=1,max_b=n;
	while(step--){
		if(aa[max_a]>bb[max_b]){
			sum1+=2;
			max_a--,max_b--;
		}else{
			if(aa[min_a]>bb[min_b]){
				sum1+=2;
				min_a++,min_b++;
			}else{
				if(aa[min_a]==bb[max_b])sum1++,sum2++; 
				else sum2+=2;
				min_a++,max_b--;
			}
		}
	}
}
signed main(){
	n=read();
	rep(i,1,n,1) a[i]=read();
	rep(i,1,n,1) b[i]=read();
	solve(a,b,ans1,ans2);
	printsp(ans1),ans1=ans2=0;
	solve(b,a,ans2,ans1);
	println(ans1);
	return 0;
}

Day 3

以后只整理我认为有价值的题(签到题不要)。

T3 - 机器人的字典

两种做法。

一、暴力

发现只有长度 \(\ge k\) 的字符串才会对答案产生影响,那么我们考虑处理出所有长度 \(\ge k\) 的字符串的前缀,然后扔进 map 里,写一个爆搜即可。我们优先选 0,其次选 1,这样就可以保证第一次遇到的合法答案就是最优解。

如果这样写的话,只能拿到 \(90pts\),会 MLE on test8。观察题面,发现该测试点的 \(k>\max{len_{s_i}}\),也就是说所有的字符串都没有用处,输出全 0 即可。

由于长度为 \(k\)\(01\) 串一共有 \(2^k\) 种,所以理论复杂度为 \(O(2^k)\)。但是仔细分析,发现最多只会枚举 \(n+1\)\(01\) 串(因为最多只有 \(n\) 个不合法前缀,最劣情况就是这 \(n\) 个前缀排在一起,因此是第 \(n+1\) 个),所以时间复杂度是 \(O(n)\)

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=2e6+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,k,maxlen;
string s[N];
map<string,bool>is_qz;
void dfs(int x,string now){
	if(x>=k){
		if(!is_qz.count(now)){
			put_str(now);
			exit(0);
		}
		return;
	}
	dfs(x+1,now+"0");
	dfs(x+1,now+"1");
}
signed main(){
	n=read(),k=read();
	rep(i,1,n,1){
		cin>>s[i];
		maxlen=max(maxlen,int(s[i].size()));
	}
	if(k>maxlen){
		rep(i,1,k,1) pt('0');
		return 0;
	}
	rep(i,1,n,1){
		if(int(s[i].size())<k)continue;
		string msc="";
		rep(j,0,k-1,1) msc+=s[i][j];
		is_qz[msc]=1;	
	}
	dfs(0,"");
	printf("dottle bot\n");
	return 0;
}

二、01Trie

\(01\) 串、前缀……这很容易令人联想到 \(01\)Trie。

考虑将所有 \(01\) 串的长度对 \(k\)\(\min\),然后插入到 \(01\)Trie 中。接着在 \(01\)Trie 上 DFS,每次贪心的向 \(0\) 搜索,并用栈存储路径,如果发现深度 \(<k\) 的空节点,就输出路径,后面用 \(0\) 补齐。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=2e6+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,k,trie[N][2],now,newnode,stk[N],tot;
string s;
void dfs(int rt,int m){
	if(!rt){
		rep(i,1,tot,1) print(stk[i]);
		rep(i,tot+1,k,1) print(0);
		exit(0); 
	}
	if(!m)return;
	stk[++tot]=0;
	dfs(trie[rt][0],m-1);
	stk[tot]=1;
	dfs(trie[rt][1],m-1);
	tot--;
}
signed main(){
	n=read(),k=read(),newnode=1;
	rep(i,1,n,1){
		cin>>s;now=1;
		rep(j,0,min(k-1,int(s.size())-1),1){
			if(!trie[now][s[j]-48])trie[now][s[j]-48]=++newnode;
			now=trie[now][s[j]-48];
		}
	}
	dfs(1,k);
	printf("dottle bot\n");
	return 0;
}

T4 - 机器人和城市 1

评价:这个题的性质不太好想,且容易被误导,想到后就好做多了。

考虑如何比较两条路径的长短:先比较最长边,然后是次长边,接下来是第三长边……换句话说,我们想让经过的 \(w\) 最小。可以发现,这就是最小生成树的性质。如果想不到这一点,就会被最短路所误导,在错误的道路上越走越远……

接下来,我们只需要根据 \(w\) 值建出最小生成树,合并时以 \(h\) 值为边权建边,最后求出最小生成树的直径即可。

树的直径有两种求法:树形 DP 和两遍 DFS 法。两遍 DFS 法不能处理负权边,但是这道题边权非负,两种做法都可以。

树形 DP:

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define int long long
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5; 
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
struct EDGE{
	int to,nxt,w;
}e[N<<2];
struct Edge{
	int s,t,w,h;
	inline bool operator<(const Edge &b)const{return w<b.w;}
}edge[N<<2];
int n,m,fa[N],siz[N],head[N<<2],cnt,dp[N],ans;
inline void addedge(int f,int t,int w){
	e[++cnt].to=t;
	e[cnt].w=w;
	e[cnt].nxt=head[f];
	head[f]=cnt;
}
inline void add_double(int f,int t,int w){
	addedge(f,t,w);
	addedge(t,f,w); 
}
inline int find(int x){
	while(x!=fa[x])x=fa[x]=fa[fa[x]];
	return fa[x];
}
inline void link(int x,int y){
	int xx=find(x),yy=find(y);
	if(xx==yy)return;
	if(siz[xx]>siz[yy])swap(xx,yy);
	fa[xx]=fa[yy],siz[yy]+=siz[xx];
}
inline void kruskal(){
	sort(edge+1,edge+m+1);
	rep(i,1,m,1){
		if(find(edge[i].s)!=find(edge[i].t)){
			link(edge[i].s,edge[i].t);
			add_double(edge[i].s,edge[i].t,edge[i].h);
		}
	}
}
void dfs(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		dfs(v,u);
		ans=max(ans,dp[u]+dp[v]+e[i].w),dp[u]=max(dp[u],dp[v]+e[i].w);
	}
}
signed main(){
	n=read(),m=read();
	rep(i,1,n,1) fa[i]=i,siz[i]=1;
	rep(i,1,m,1) edge[i].s=read(),edge[i].t=read(),edge[i].w=read(),edge[i].h=read();
	kruskal();
	dfs(1,0);
	println(ans);
	return 0;
}

两遍 DFS 法:

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define int long long
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5; 
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
struct EDGE{
	int to,nxt,w;
}e[N<<2];
struct Edge{
	int s,t,w,h;
	inline bool operator<(const Edge &b)const{return w<b.w;}
}edge[N<<2];
int n,m,fa[N],siz[N],head[N<<2],cnt,dp[N],ans,mxid,dis[N];
inline void addedge(int f,int t,int w){
	e[++cnt].to=t;
	e[cnt].w=w;
	e[cnt].nxt=head[f];
	head[f]=cnt;
}
inline void add_double(int f,int t,int w){
	addedge(f,t,w);
	addedge(t,f,w); 
}
inline int find(int x){
	while(x!=fa[x])x=fa[x]=fa[fa[x]];
	return fa[x];
}
inline void link(int x,int y){
	int xx=find(x),yy=find(y);
	if(xx==yy)return;
	if(siz[xx]>siz[yy])swap(xx,yy);
	fa[xx]=fa[yy],siz[yy]+=siz[xx];
}
inline void kruskal(){
	sort(edge+1,edge+m+1);
	rep(i,1,m,1){
		if(find(edge[i].s)!=find(edge[i].t)){
			link(edge[i].s,edge[i].t);
			add_double(edge[i].s,edge[i].t,edge[i].h);
		}
	}
}
void dfs(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		dis[v]=dis[u]+e[i].w;
		dfs(v,u);
	}
	if(dis[u]>ans)ans=dis[u],mxid=u;
}
signed main(){
	n=read(),m=read();
	rep(i,1,n,1) fa[i]=i,siz[i]=1;
	rep(i,1,m,1) edge[i].s=read(),edge[i].t=read(),edge[i].w=read(),edge[i].h=read();
	kruskal();
	dfs(1,0);
	dis[mxid]=0,ans=-1;
	dfs(mxid,0);
	println(ans);
	return 0;
}

Day 4

T2 - 机器人与 CFK

贪心。

我们考虑优先选择原价与降价后差值大的,然后用 map 记录使用的次数(周数),判断是否 \(>m\) 即可。

T3 - 机器人的图

根据惯例,暴力分会很足,因此直接写一个暴力:

dfs(u,lstval,len) 表示当前在点 \(u\),上一条边的权值为 \(lstval\),总共经过了 \(len\) 条边。因为点数等于边数加一,所以实际上要求的东西是 \(len-1\)。注意到图不一定连通,那么我们从每个点出发都跑一遍即可。

大样例也可以很快跑过。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
struct edge{
	int to,nxt,w;
}e[N<<2];
int head[N<<2],cnt,n,m,ans;
inline void addedge(int f,int t,int w){
	e[++cnt].to=t;
	e[cnt].w=w;
	e[cnt].nxt=head[f];
	head[f]=cnt;
}
inline void add_double(int f,int t,int w){
	addedge(f,t,w);
	addedge(t,f,w);
}
void dfs(int u,int lstval,int len){
	if(!lstval){
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			ans=max(ans,len+1);
			dfs(v,e[i].w,len+1);
		}
		return;
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(e[i].w!=lstval+1)continue;
		ans=max(ans,len+1);
		dfs(v,e[i].w,len+1);
	}
}
signed main(){
	n=read(),m=read();
	rep(i,1,m,1){
		int f=read(),t=read();
		char ch;
		scanf("%c",&ch);
		add_double(f,t,ch-'a'+1);
	}
	rep(i,1,n,1) dfs(i,0,0);
	println(ans);
	return 0;
}

这样就拿到了 \(95pts\)

仔细观察,发现 TLE 的数据只有两个点,却有 \(10^5\) 条边,爆搜就这样被卡到了指数级。

然后加个记忆化就过了。/kk

同样的,这道题我们也可以使用图上动态规划来解决。

我们设 \(dp_{u,w}\) 表示当前在点 \(u\),上一条边权值为 \(w\) 时的答案。

此时有转移:

\[dp_{u,j}=\max(dp_{u,j},dp_{v,j-1}+1)[(u,v)\in E,w(u,v)=j] \]

这个时候我们发现甚至不需要邻接表,只需要记录每条边的起点、终点和权值就行了。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
struct Edge{
	int u,v,w;
	Edge(int _u=0,int _v=0,int _w=0):u(_u),v(_v),w(_w){}
}edge[N];
int n,m,dp[N][30],ans;
signed main(){
	n=read(),m=read();
	rep(i,1,m,1){
		int u=read(),v=read();
		char ch;scanf("%c",&ch);
		edge[i]=Edge(u,v,ch-'a'+1);
	}
	rep(j,1,26,1) rep(i,1,m,1){
		if(edge[i].w==j){
			dp[edge[i].u][j]=max(dp[edge[i].u][j],dp[edge[i].v][j-1]+1);
			dp[edge[i].v][j]=max(dp[edge[i].v][j],dp[edge[i].u][j-1]+1);
		}
	}
	rep(j,1,26,1) rep(i,1,n,1) ans=max(ans,dp[i][j]);
	println(ans);
	return 0;
}

考虑一个加强版的问题:此时的边权不再是 \(1-26\),而是 \(10^{18}\) 级别,可以做吗?

答案是可以的!

我们观察一下原题转移:可以发现,if 语句执行的次数不会超过 \(m\) 次。

这就说明了,我们的转移数量是和 \(m\) 挂钩的,而其他没有必要的枚举,我们完全可以剪掉。

具体的,我们存储一下读入的边权(显然没有被读入的边权都是无效的),然后开一个 vector,下标为边权,值为 pair,存储起点和终点。dp 数组不变,但是边权过大,可以使用 map 维护(包括 vector)。

然后,我们将边权数组去重。现在,里面的都是出现过的所有边权,并且有序。我们枚举边权,对于当前边权,只要它不是最小的,且不存在边权为当前权值 \(-1\) 的边(直接访问前一个边权),就退出。

接下来,我们取出当前边权为下标的 vector 中的所有元素,套用之前的转移方程进行转移。最后注意一下答案的计算:这时显然不能转移完再计算。注意到没有转移完的值必然不会超过最终值,因此可以边转移边更新答案

下面我们来看一下代码:

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
    int x=0;bool sgn=0;char ch=gt();
    while(!__(ch)){sgn|=(ch=='-');ch=gt();}
    while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
    return sgn?-x:x;
}
inline void print(int x){
    static char st[70];short top=0;
    if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
    static char st[70];short top=0;
    if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
    static char st[70];short top=0;
    if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
    int siz=s.size();
    rep(i,0,siz-1,1) pt(s[i]);
    printf("\n");
}
struct Edge{
    int u,v,w;
    Edge(int _u=0,int _v=0,int _w=0):u(_u),v(_v),w(_w){}
}edge[N];
int n,m,ans;
ll val[N];
struct Pair{
	int fi,se;
	Pair(int _fi=0,int _se=0):fi(_fi),se(_se){}
	inline bool operator<(const Pair &b)const{return fi!=b.fi?fi<b.fi:se<b.se;}
};
map<int,map<ll,int> >dp;
map<ll,vector<Pair> >vec;
signed main(){
    n=read(),m=read();
    for(int i=1;i<=m;++i){
        int u=read(),v=read(),w=read();
        edge[i]=Edge(u,v,w);
        val[i]=w,vec[val[i]].emplace_back(Pair(u,v));
    }
    sort(val+1,val+m+1);
    int new_m=unique(val+1,val+m+1)-(val+1);
    for(int i=1;i<=new_m;++i){
		if(i!=1&&val[i-1]+1!=val[i])continue;
		for(Pair j:vec[val[i]]){
			int u=j.fi,v=j.se;
			dp[u][val[i]]=max(dp[u][val[i]],dp[v][val[i]-1]+1);
            dp[v][val[i]]=max(dp[v][val[i]],dp[u][val[i]-1]+1);
            ans=max(ans,max(dp[u][val[i]],dp[v][val[i]]));
		} 
	}
    println(ans);
    return 0;
}

缺点就是常数可能较大,但是开个 O2 应该就没问题了。

T4 - 机器人吃东西

我们考虑区间 DP。

首先需要发现某性质:如果可以将所有数删除,那么一定存在一种方案,使得所有数都被消除掉。

证明:如果一段区间能被消掉,就考虑连着其周围的一起消掉。由于最终是能消完的,所有这样做可行。

\(dp_{l,r}\) 表示区间 \([l,r]\) 的最大消除次数。

我们发现这样不好转移。考虑对于每一段区间 \([l,r]\),都存在一段从小到大的 \(w_1,w_2...w_k\),使得它们是最后一次被删除掉的数。我们尝试枚举 \(w_1\),这时可以发现,\(w_1\) 左边不存在 \(w\) 中的元素,因此无需多考虑;而右边的区间消除完后,正好会留下 \(w_2,w_3...w_n\),与左边的 \(w_1\) 恰好能够消除。

我们考虑增设一维状态:\(dp_{l,r,x}\) 表示区间 \([l,r]\) 左边多 & 上一个 \(x\) 的答案。如果没有或者为 \(0\),则设为 \(63(2^6-1)\)

那么就有转移:

\[dp_{l,r,x}=\max_{l\le i\le r}{(dp_{l,i-1,63}+dp_{i+1,r,x \& a_i)}}+[x==0] \]

发现这样递推不是很方便,可以使用记忆化搜索的形式,不用担心搜索顺序的问题,也十分好写。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,a[70],dp[70][70][70];
int dfs(int l,int r,int x){
	if(dp[l][r][x]!=-1)return dp[l][r][x];
	int sum=0,X=(x==0)?63:x;
	rep(i,l,r,1) sum=max(sum,dfs(l,i-1,63)+dfs(i+1,r,X&a[i]));
	return dp[l][r][x]=sum+(x==0);
}
signed main(){
	n=read();
	rep(i,1,n,1) a[i]=read();
	rep(i,0,65,1) rep(j,0,65,1) rep(k,0,65,1) dp[i][j][k]=-1;
	println(dfs(1,n,63));
	return 0;
}

Day 5

T3 - 六边形领域

观察网格,我们可以发现只有横边才可以移动较多的横坐标。我们只考虑从起点到终点需要走多少条横边,通过找规律,我们发现如果 \(2x+y>0\),答案为 \(\lfloor \dfrac{2x+y}{3} \rfloor\),否则为 \(\lfloor \dfrac{-2x-y+1}{3} \rfloor\)

既然横着的边需要走这么多条,那么其他方向的边同理。那么我们将坐标系旋转 \(120^{\circ}\),这样其他方向的边就会转为横边,重复两次统计答案即可。

最后,如果坐标系旋转 \(120^{\circ}\)\((x,y)\) 会变为 \((y,-x-y)\),画一下图计算即可。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define int long long
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int x,y,ans;
inline void rotate(int &x,int &y){
	int xx=y,yy=-x-y;
	x=xx,y=yy;
}
inline int getans(int x,int y){
	int u=x+x+y;
	return (u>0)?(u/3):((-u+1)/3); 
}
signed main(){
	freopen("hexagon.in","r",stdin);
	freopen("hexagon.out","w",stdout);
	x=read(),y=read();
	ans+=getans(x,y);
	rotate(x,y);
	ans+=getans(x,y);
	rotate(x,y);
	ans+=getans(x,y);
	println(ans);
	return 0;
}

T4 - 染色

我们考虑两种情况。

注意 \(a_i\) 为奇数,所以当 \(n\) 为偶数的时候,\(12\) 交替染色即可,只需要两种颜色。

\(n\) 为奇数时,两种颜色就不够了,我们考虑使用三种颜色:

\(12121212...3232323...1313131313\)

这样一共分成三段,每一段长度大约为 \(\dfrac{n}{3}\)

这里注意一个细节,如果第一段的长度为奇数,那么将会合不上口,调整其与第二段的长度即可。

然后你就可以发现甚至不需要读入 \(m\)\(a_i\)

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n;
signed main(){
	freopen("coloring.in","r",stdin);
	freopen("coloring.out","w",stdout);
	n=read();
	if(n%2==0){
		printf("2\n");
		rep(i,1,n,1) printsp(i%2+1);
		return 0;
	}
	printf("3\n");
	int a=n/3,b=n/3;
	if(a%2==1)a++,b--;
	rep(i,1,a,1) printsp((i+1)%2+1);
	rep(i,a+1,n-b+1,1) printsp(i%2+2);
	rep(i,n-b+2,n,1) printsp((i%2)*2+1);
	return 0;
}

Day 6

T3 - 投票

形式化题意:求

\[ans_k=\sum_{i=0}^{v_k}\binom{k}{i}(0\le k< n) \]

其中,\(v_i\) 表示第 \(i\) 个人投票完,场上可以投反票的数量。显然有 \(v_0=\lfloor \dfrac{n}{2} \rfloor\)

根据组合数递推公式,有 \(\binom{i}{j}=\binom{i-1}{j}+\binom{i-1}{j-1}\)。那么原式就可以被拆分为:

\[ans_k=\sum_{i=0}^{v_k}\binom{k-1}{i}+\sum_{i=0}^{v_k-1}\binom{k-1}{i} \]

根据题目的性质,\(v_{k-1}\le v_k\le v_{k-1}+1\),所以 \(ans_k\) 就等于 \(ans_{k-1}\times 2\) 加上或减去 \(\binom{k-1}{v_k}\)

这样我们每一次只需要算一个组合数即可。考虑倒着做
,预处理一下阶乘和阶乘逆元就可以做到 \(O(n)\)

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define int long long
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=2e5+5;
const int MOD=998244353;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,fac[N],invfac[N],v[N],ans[N];
inline int power(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%MOD;
		a=a*a%MOD,b>>=1;
	}
	return res;
}
inline int C(int n,int m){
	if(n<m||n<0||m<0)return 0;
	return fac[n]*invfac[m]%MOD*invfac[n-m]%MOD;
}
signed main(){
	n=read(),fac[0]=1,v[0]=n/2;
	rep(i,1,n,1) v[i]=v[i-1]-(read()==0);
	rep(i,1,n,1) fac[i]=(fac[i-1]*i)%MOD;
	invfac[n]=power(fac[n],MOD-2);
	per(i,n-1,0,1) invfac[i]=(invfac[i+1]*(i+1))%MOD;
	ans[n]=(v[n]>=0);
	per(i,n-1,0,1){
		ans[i]=(ans[i+1]<<1)%MOD;
		int now=C(n-i-1,v[i]);
		if(v[i]==v[i+1])ans[i]=((ans[i]-now)%MOD+MOD)%MOD;
		else ans[i]=(ans[i]+now)%MOD;
	}
	rep(i,0,n-1,1) println(ans[i]);
	return 0;
}

T4 - 字符串

首先,我们最终凑出来的 \(T\) 一定要在 \(S\) 中至少出现一次,否则答案为 \(0\)

那么,对于 \(S\) 中的每一个子串,我们需要求出该子串能否被组出以及其在原串中的出现次数。

对于求每个子串的出现次数,我们可以求出每一对后缀的最长公共前缀,然后统计该子串左端点对应后缀和多少个其他后缀的最长公共前缀长度大于该子串长度。

对于最长公共前缀的长度,我们考虑通过 DP 求出。设 \(dp_{i,j}\) 表示以 \(i\) 开头的后缀和以 \(j\) 开头的后缀的最长公共前缀的长度。显然,如果 \(s_i\not=s_j\),则 \(dp_{i.j}=0\),否则 \(dp_{i,j}=dp_{i+1,j+1}\),倒着递推即可。

然后,对于每个子串是否能被组出,我们可以抽象成一张图。如果区间 \([l,r]\)\(T\) 中出现过,就向 \(l->r+1\) 连一条边。最后,如果存在一条从 \(L\)\(R+1\)路径,就说明 \([L,R]\) 可以被组出。

容易发现这就是裸的传递闭包,使用 bitset 优化即可做到 \(O(\dfrac{n^3}{w})\) 的时间复杂度,可以通过本题。

这里扔一下 qlr 的 SAM 写法,可以做到更优秀的时间复杂度。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=2005;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(ll x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
bitset<N>dp[N+N],f[N+N],g[N+N];
int num[N+N],n,m,ans;
char s[N],t[N];
struct SAMNode{
	int len,fa,ch[30];
};
struct Suffix_Automaton{
	SAMNode pos[N+N];
	int cnt=1,lst=1,now;
	vector<int>ch[N+N];
	ll ans;
	inline void insert(int c){
		int p=lst,np=lst=++cnt;
		dp[np][++now]=num[np]=1,pos[np].len=pos[p].len+1;
		while(p&&!pos[p].ch[c])pos[p].ch[c]=np,p=pos[p].fa;
		if(!p)pos[np].fa=1;
		else{
			int q=pos[p].ch[c];
			if(pos[q].len==pos[p].len+1)pos[np].fa=q;
			else{
				int nq=++cnt;
				pos[nq]=pos[q],pos[nq].len=pos[p].len+1,pos[np].fa=pos[q].fa=nq;
				while(p&&pos[p].ch[c]==q)pos[p].ch[c]=nq,p=pos[p].fa;
			}
		}
	}
	void dfs(int p){
		rep(i,0,ch[p].size()-1,1){
			dfs(ch[p][i]);
			dp[p]|=dp[ch[p][i]],num[p]+=num[ch[p][i]];
		}
	}
}SAM;
signed main(){
	scanf("%s",s+1);
	n=strlen(s+1),m=read();
	rep(i,1,n,1) SAM.insert(s[i]-'a');
	rep(i,2,SAM.cnt,1) SAM.ch[SAM.pos[i].fa].emplace_back(i);
	SAM.dfs(1);
	rep(i,1,m,1){
		scanf("%s",t+1);
		int len=strlen(t+1),p=1;
		rep(j,1,len,1)p=SAM.pos[p].ch[t[j]-'a'];
		rep(j,1,n,1) if(dp[p][j])f[j][j-len]=1;
	}
	rep(i,0,n,1) g[i][i]=1;
	rep(i,1,n,1) rep(j,0,i-1,1) if(f[i][j])g[i]|=g[j];
	rep(i,1,n,1){
		int p=1;
		rep(j,i,n,1){
			p=SAM.pos[p].ch[s[j]-'a'];
			if(g[j][i-1])ans=max(ans,num[p]*(j-i+1));		 
		}
	}
	println(ans);
	return 0;
}

Day 7

T1 - 划分

本来这题没什么好说的,但是这里我讲一种线性求严格众数的方法。

摩尔投票:主要分为抵消和计数两个阶段。我们记录两个变量 \(x\)\(y\) 表示当前可能的区间众数和 \(x\) 的票数。我们从左往右扫,如果 \(a_i\not=x\),那么 \(y\) 减一,否则加一。如果 \(x\) 变为负数,那么就把 \(x\) 设为当前的 \(a_i\),并把票数 \(y\) 设为一。最后,我们需要验证当前的 \(x\) 是否是真正的严格整数,重新遍历一次统计即可。

根据题目性质,可以发现如果整个区间存在严格整数,那么无论怎么选都无法满足条件;否则,直接选整段区间即可。可以使用类似归纳法的方法证明。最后,我们只需要用摩尔投票求出区间内是否有严格众数即可。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int T,n,a[N];
signed main(){
	T=read();
	while(T--){
		n=read();
		int x=0,y=0;
		rep(i,1,n,1){
			a[i]=read();
			if(a[i]==x)y++;
			else if(--y<0)x=a[i],y=0;
		}
		y=0;
		rep(i,1,n,1) y+=(a[i]==x?1:-1);
		printf(y<=0?"Yes\n":"No\n");
	}
	return 0;
}

T3 - 集合

形式化题意:需要移动 \(l\)\(r\) 指针,使得 \([l,r]\) 中存在 \(x\)\(y\),满足 \(a_x=a_y\)。求最小的移动次数。

根据题意,我们可以知道,在 \([x,y]\) 中,不存在更小的 \(z\) 满足 \(a_x=a_z\),所以我们考虑预处理出这些 \((x,y)\)

分类讨论:

  • \(x<y<i\)

实际上这就等价于最大化 \(x\),对于预处理出的 \((x,y)\) 滚一次前缀 \(\max\) 即可。

  • \(i<x<y\)

与第一种情况类似。对于所有满足条件的 \(y\) 滚一次后缀 \(\min\) 即可。

  • \(x\le i\le y\)

实际上等价于对于我们预处理出的所有 \((x,y)\),求出 \(\min_{y-x}\)。实际上这就是一个前缀 \(\min\),不涉及区间减法,因此直接树状数组维护即可。当然用 set 也可以。

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define lowbit(x) x&-x
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=2e5+5;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
int n,a[N],l[N],r[N],ans[N],tot;
vector<int>insert[N];
struct Pair{
	int fi,se;
	Pair(int _fi=0,int _se=0):fi(_fi),se(_se){}
}v[N];
map<int,int>lst;
struct fenwick{
	int n,c[N<<2];
	inline void clear(){memset(c,0,sizeof(c));}
	inline void add(int x,int k){rep(i,x,n,lowbit(i)) c[i]=min(c[i],k);}
	inline int sum(int x){int res=2147283647;per(i,x,1,lowbit(i)){res=min(res,c[i]);}return res;}
	inline void update(int l,int r,int k){add(l,k);add(r+1,-k);}
	inline int query(int l,int r){return sum(r)-sum(l-1);}
}BIT;
signed main(){
	BIT.n=n=read();
	rep(i,1,n,1){
		a[i]=read();
		if(lst.count(a[i]))v[++tot]=Pair(lst[a[i]],i);
		lst[a[i]]=i; 
	}
	memset(l,-0x3f,sizeof(l));
	memset(r,0x3f,sizeof(r));
	rep(i,1,tot,1) l[v[i].se]=max(l[v[i].se],v[i].fi);
	rep(i,1,tot,1) r[v[i].fi]=min(r[v[i].fi],v[i].se);
	rep(i,1,n,1) l[i]=max(l[i],l[i-1]);
	per(i,n,1,1) r[i]=min(r[i],r[i+1]);
	rep(i,1,tot,1) insert[v[i].se].emplace_back(v[i].fi);
	memset(BIT.c,0x3f,sizeof(BIT.c));
	per(i,n,1,1){
		for(int j:insert[i]) BIT.add(j,i-j);
		ans[i]=min(BIT.sum(i),min(i-l[i],r[i]-i));
	}
	rep(i,1,n,1) printsp(ans[i]);
	return 0;
}

T4 - 今晚九点,小王唱歌,不见不散。

对于 \(k=0\) 的部分分,直接 dijkstra 即可。

仔细分析,如果坐车坐了多站,等价于讲路程拆分成若干段,然后在每一段的结尾下车再上车。

那么我们就可以写一个 struct 来维护这些段。其中, st 表示这一段的开始时间,ed 表示这一段的结束时间,to 表示这一段的终点。

我们发现,这些段可以被拆分成若干个 pairfrom 表示起点,to 表示终点,那么我们就可以使用一个 vector 来存储这些 struct。其中,下标为 from,元素为 struct

我们在求最短路的时候,我们遍历 vector\(u\) 为下标的所有 struct。如果 \(dis_{u}\le st\),那么我们将 \(dis_{to}\)\(ed\)\(\min\)

最后,由于是稀疏图,使用堆优化的 dijkstra 跑最短路即可。注意 \(1\)\(n\) 不连通的时候答案为 \(-1\)。还有 \(6\times 10^5\times10^9\) 会炸 int,要开 long long

#include<bits/stdc++.h>
//#include<bits/extc++.h>
//#pragma GCC optimize("Ofast")
#define gt getchar
#define pt putchar
#define y1 y233
#define int long long
#define rep(i,a,b,k) for(int (i)=(a),(_)=(b);(i)<=(_);(i)+=(k))
#define per(i,a,b,k) for(int (i)=(a),(_)=(b);(i)>=(_);(i)-=(k))
#define edgerep(i,h,u,e) for(int (i)=h[(u)];(i);(i)=e[(i)].nxt)
typedef long long ll;
//typedef __int128 lll;
typedef unsigned long long ull;
const int N=1e5+5;
const int M=3e5+5;
const int infinity=1e18;
using namespace std;
//using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
inline int read(){
   	int x=0;bool sgn=0;char ch=gt();
   	while(!__(ch)){sgn|=(ch=='-');ch=gt();}
   	while(__(ch)){x=(x<<1)+(x<<3)+(ch-'0');ch=gt();}
	return sgn?-x:x;
}
inline void print(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);
}
inline void printsp(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(32);
}
inline void println(int x){
	static char st[70];short top=0;
	if(x<0)pt('-'),x=-x;
    do{st[++top]=(x%10+'0'),x/=10;}while(x);
    while(top)pt(st[top--]);pt(10);
}
inline void put_str(string s){
	int siz=s.size();
	rep(i,0,siz-1,1) pt(s[i]);
	printf("\n");
}
struct Edge{
	int to,nxt,w; 
}e[M<<2];
struct Edge2{
	int st,ed,to;
	Edge2(int _u=0,int _v=0,int _w=0):st(_u),ed(_v),to(_w){}
}edge[M<<2];
struct Pair{
	int val,pos;
	Pair(int _val=0,int _pos=0):val(_val),pos(_pos){}
	bool operator<(const Pair &b)const{return val>b.val;}
};
priority_queue<Pair>pq;
int head[M<<2],cnt,n,m,k,dis[N];
bool vis[N];
vector<Edge2>G[N];
inline void addedge(int f,int t,int w){
	e[++cnt].to=t;
	e[cnt].w=w;
	e[cnt].nxt=head[f];
	head[f]=cnt;
}
inline void add_double(int f,int t,int w){
	addedge(f,t,w);
	addedge(t,f,w);
}
inline void insert(int fr,int t0,int t1,int to){
	G[fr].emplace_back(Edge2(t0,t1,to));
}
inline void dijkstra(int s){
	rep(i,0,n,1) dis[i]=infinity,vis[i]=0;
	dis[s]=0;
	pq.push(Pair(0ll,s));
	while(pq.size()){
		int u=pq.top().pos;
		pq.pop();
		if(vis[u])continue;
		else vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				pq.push(Pair(dis[v],v));
			}
		}
		for(auto V:G[u]){
			int v=V.to;
			if(dis[u]<=V.st&&dis[v]>V.ed){
				dis[v]=V.ed;
				pq.push(Pair(dis[v],v));
			}
		}
	}
}
signed main(){
	n=read(),m=read(),k=read();
	rep(i,1,m,1){
		int u=read(),v=read(),w=read();
		add_double(u,v,w);
	}
	rep(i,1,k,1){
		int p=read();
		int st=read(),fr=read(),ed,to;
		rep(j,2,p,1){
			ed=read(),to=read();
			insert(fr,st,ed,to);
			st=ed,fr=to;
		}
	}
	dijkstra(1);
	if(dis[n]==infinity)printf("-1\n");
	else println(dis[n]);
	return 0;
}
posted @ 2024-02-28 15:30  Southern_Dynasty  阅读(19)  评论(1编辑  收藏  举报