24.4.7周报(kmp+状压)

星期一:

再学kmp,学的最明白的一次

贴道kmp的题                         洛谷传送门

思路:答案为n-ne【n】,把字符串画两遍理解一下

思路:最长周期,复制一遍过后要求覆盖原字符串,及字符串中非周期的后缀与周期的部分前缀相等,因为周期要最长,所以后缀要最短,即求大于0的情况下最短相等前后缀,依然能用next数组求,不断j=ne【j】即可,记得压缩路径,不然会 T

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int ne[N];
void solve(){
	string s; cin >> n >> s; s=" "+s;
	ne[1]=0;
	for(int i=2,j=0;i<=n;i++){
		while(j && s[i]!=s[j+1]) j=ne[j];
		if(s[i]==s[j+1]) j++;
		ne[i]=j;
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		if(!ne[i]) continue;
		int j=ne[i];
		while(ne[j]) j=ne[j];
		ne[i]=j;               //路径压缩
		ans+=i-j;
	}
	cout << ans;
}

星期二:

历时两天,终于拿下这题                    atc传送门

思路:kmp加上状压板子,不难,但做这题的时间跨度很长

sa【i】【j】表示字符串 j 接在 i 后面实际增加的长度,用kmp预处理出来

wa了很多发是因为去掉被包含的字符串时,一边遍历vector一边erase,后来加了个临时储存的vector,一个个添加到ves里就过了,教训是谨慎使用erase,特别是遍历时

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
vector<string>ves;
int ne[N];
int sa[22][22];
ll dp[1<<21][22];
bool check(string s1,string s2){
	int m=s1.size(),n=s2.size();
	s1=" "+s1,s2=" "+s2;
	ne[1]=0;
	for(int i=2,j=0;i<=n;i++){
		while(j && s2[i]!=s2[j+1]) j=ne[j];
		if(s2[i]==s2[j+1]) j++;
		ne[i]=j;
	}
	for(int i=1,j=0;i<=m;i++){
		while(j && s1[i]!=s2[j+1]) j=ne[j];
		if(s1[i]==s2[j+1]) j++;
		if(j==n) return 1;
	}
	return 0;
}
int ask(string s1,string s2){
	string s=" "+s2+s1;int n=s.size()-1;
	ne[1]=0;
	for(int i=2,j=0;i<=n;i++){
		while(j && s[i]!=s[j+1]) j=ne[j];
		if(s[i]==s[j+1]) j++;
		ne[i]=j;
	}
	return ne[n];
}
void solve(){
	cin >> n;
	vector<string>tmp;
	for(int i=1;i<=n;i++){
		string s; cin >> s;
		tmp.push_back(s);
	}
	sort(tmp.begin(),tmp.end());
	tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
	for(int i=0,sz=tmp.size();i<sz;i++){
		bool if1=1;
		for(int j=0;j<sz;j++){
			if(i==j) continue;
			if(check(tmp[j],tmp[i])) if1=0;
		}
		if(if1) ves.push_back(tmp[i]);
	}
	n=ves.size();
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(i==j) continue;
			sa[i][j]=ves[j].size()-ask(ves[i],ves[j]);
		}
	}
	for(int mask=0;mask<=(1<<n);mask++){
		for(int i=0;i<=n;i++) dp[mask][i]=1e18;
	}
	for(int i=0;i<n;i++) dp[1<<i][i]=ves[i].size();
	for(int mask=0;mask<(1<<n);mask++){
		for(int i=0;i<n;i++){
			if(!(mask&1<<i)) continue;
			for(int j=0;j<n;j++){
				if(mask&1<<j) continue;
				int nmask=mask|(1<<j);
				dp[nmask][j]=min(dp[mask][i]+sa[i][j],dp[nmask][j]);
			}
		}
	}
	ll ans=1e18;
	for(int i=0;i<n;i++) ans=min(dp[(1<<n)-1][i],ans);
	cout << ans;
}

下午蓝桥模拟赛,打的还行

一道全排列暴力的题,没有思路,贴上是因为对于飞机降落的时间判断失误,痛失60分,贴着给自己长个记性

思路:区间dp,遇到两次都是用暴力冲过去的

dp【i】【j】表示区间 i,j 是否可翻转

转移:若s【i】> s【j】,dp【i】【j】=1,否则若s【i】== s【j】,dp【i】【j】=dp【i+1】【j-1】

代码如下:

ll n;
string s;
ll dp[5050][5050];
void solve(){
	cin >> s;
	n=s.size();s=" "+s;
	for(int i=1;i<n;i++)
		if(s[i]>s[i+1]) dp[i][i+1]=1;
	for(int len=3;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			if(s[l]>s[r]) dp[l][r]=1;
			else if(s[l]==s[r]) dp[l][r]=dp[l+1][r-1];
		}
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++)
			ans+=dp[i][j];
	}
	cout << ans;
}

星期三:

背包dp第二题:                        cf传送门

思路:dp【i】【j】表示考虑到第 i 个,放了 j 个数的最大价值

转移:dp【i】【j】从dp【i】【j-1】或dp【i-1】【j】转移过来

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll p,q,r;
int a[N];
ll dp[N][4];
void solve(){
	cin >> n >> p >> q >> r;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		dp[i][1]=dp[i][2]=dp[i][3]=-1e18;
	}
	dp[1][1]=p*a[1];
	dp[1][2]=(p+q)*a[1];
	dp[1][3]=(p+q+r)*a[1];
	for(int i=2;i<=n;i++){
		dp[i][1]=max(dp[i-1][1],p*a[i]);
		dp[i][2]=max(dp[i][1]+q*a[i],dp[i-1][2]);
		dp[i][3]=max(dp[i][2]+r*a[i],dp[i-1][3]);
	}
	cout << dp[n][3];
}

一天vp了两场cf global round,越vp越对6号晚上没信心,上午那场卡A,晚上这场卡B

A就不说了,纯属我脑子不好使,贴下第二场的B                 cf传送门

思路:答案为最大值 除以 所有数的gcd,有点赛时猜结论的风格

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int a[N],gc;
int gcd(int a,int b){
	return b==0?a:gcd(b,a%b);
}
void solve(){
	cin >> n;
	gc=0;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		gc=gcd(a[i],gc);
	}
	cout << a[n]/gc << "\n";
}

就酱紫,好好休息,清明节假期猛猛练

星期四:

cf global round 23,B题越看越不想看,跳了,补C                       cf传送门

思路:比较简单,因为操作很强,逆序对必能都填上

贴这题是因为使用vector时又忘了判是否为空了

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int p[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> p[i];
	vector<PII>ve;
	for(int i=1;i<n;i++)
		if(p[i]>p[i+1])
			ve.push_back({p[i]-p[i+1],i+1});
	sort(ve.begin(),ve.end(),greater<PII>());
	if(ve.empty()){
		for(int i=1;i<=n;i++) cout << "1 ";
		cout << "\n";
		return ;
	}
	for(int i=1;i<=n;i++){
		if(ve.size() && i>=ve.back().first) cout << ve.back().second << " ",ve.pop_back();
		else cout << 1 << " ";
	}
	cout << "\n";
}

接着补D                                           cf传送门

思路:树上dp,贪心的想每条路径一定会从根节点到子节点

假设父节点有c条路径,sz个子节点,根据题意,每个子节点分配的路径数为c/sz(向下取整 或 c/sz(向上取整,算出子节点分配路径为向上和向下取整后的值,根据其差值排序,前c %sz个子节点向上取整

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int s[N],k;
vector<int>ve[N];
ll dp[N][2];
void dfs(int x,int c){            //节点为x,有c条路径在此节点
	dp[x][0]=1ll*s[x]*c;
	dp[x][1]=1ll*s[x]*(c+1);
	if(ve[x].empty()) return ;
	int sz=ve[x].size();
	vector<int>tmp;
	for(auto i:ve[x])
		dfs(i,c/sz),tmp.push_back(i);
	for(auto i:tmp) dp[i][1]-=dp[i][0];
	sort(tmp.begin(),tmp.end(),[&](int a,int b){
		return dp[a][1]>dp[b][1];
	});
	int lef=c%sz;
	for(int i=0,tsz=tmp.size();i<tsz;i++){
		dp[x][0]+=dp[tmp[i]][0];
		if(i<lef) dp[x][0]+=dp[tmp[i]][1];
		dp[x][1]+=dp[tmp[i]][0];
		if(i<=lef) dp[x][1]+=dp[tmp[i]][1];   //父节点向上取整也可以给子节点多分配一个
	}
}
void solve(){
	cin >> n >> k;
	for(int i=1;i<=n;i++) ve[i].clear();
	for(int i=2;i<=n;i++){
		int p; cin >> p;
		ve[p].push_back(i);
	}
	for(int i=1;i<=n;i++) cin >> s[i];
	dfs(1,k);
	cout << dp[1][0] << "\n";
}

cf global round 24 C                                    cf传送门

思路:把数分为两个集合,一个集合为大数,另一个存小数,两集合间任意建边,边数为sum1 * sum2,遍历一遍大数小数的界限,记录最大答案即可

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int a[N];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++) cin >> a[i];
	sort(a+1,a+n+1);
	if(a[1]==a[n]){
		cout << n/2 << "\n";
		return ;
	}
	ll ans=0;
	for(int i=1;i<=n;i++){
		while(a[i]==a[i+1]) i++;
		ll sum1=i,sum2=n-i;
		ans=max(sum1*sum2,ans);
	}
	cout << ans << "\n";
}

星期五:

下午蓝桥训练赛,被dp拿下了,准备补题

晚上牛客小白月赛,做到了E, STL题,还不错

dp题单数位dp第三题:                              cf传送门

思路:假设先对n作一步处理, 结果为1000以内的数,把1000以内的数到1的操作数预处理出来

然后开始填数,保证其小于等于n,填好后即为未操作过的数,1的数量cnt1即为操作一步后的数,若cnt1到1的操作数为k-1,即符合条件

特判,k为0,ans为1,k为1时,判定条件为cnt【cnt1】==0,1也会被算进答案里,故答案减1

代码如下:

ll n;
string s;
int k;
int num[1010],cnt[1010];
ll dp[1010][1010][2];
int getc(int x){
	int res=0;
	while(x!=1) x=__builtin_popcount(x),res++;
	return res;
}
int dfs(int lef,int cnt1,int if1){       //还剩lef长度没填,目前填了cnt1个1,是否为上界状态
	if(!lef){
		if(!cnt1) return 0;
		return cnt[cnt1]==k-1;         //一步操作为cnt1,再k-1步操作为1,则合法
	}
	if(dp[lef][cnt1][if1]!=-1)           //记忆化处理
		return dp[lef][cnt1][if1];
	ll res=0;
	if(if1){                             //为上界状态
		if(num[lef]){                    
			res+=dfs(lef-1,cnt1+1,1),res%=mod;   //填1,保持上界状态
			res+=dfs(lef-1,cnt1,0),res%=mod;
		}else res+=dfs(lef-1,cnt1,1),res%=mod;   //这位为0,就只能填0
	}else{                                      //否则填0或1均可
		res+=dfs(lef-1,cnt1+1,0),res%=mod;
		res+=dfs(lef-1,cnt1,0),res%=mod;
		
	}
	return dp[lef][cnt1][if1]=res;             //记忆化处理
}
void solve(){
	cin >> s >> k;
	if(!k){cout << 1; return ;}
	for(int i=1;i<=1000;i++)                  //预处理操作数
		cnt[i]=getc(i);
	int sz=s.size();
	for(int i=1;i<=sz;i++) num[i]=s[sz-i]-'0';
	memset(dp,-1,sizeof dp);
	ll ans=dfs(sz,0,1);
	if(k==1) ans--;
	cout << ans;
}

星期六:

蓝桥杯补题:

思路:前缀和预处理

(a【j】- a【i-1】)%k==0,可以转化为a【j】%k==a【i-1】%k

代码如下:

ll n;
int sum,k;
map<int,int>mp;
void solve(){
	cin >> n >> k;
	ll ans=0;
	mp[0]=1;
	for(int i=1;i<=n;i++){
		int x; cin >> x;
		sum+=x,sum%=k;
		ans+=mp[sum];
		mp[sum]++;
	}
	cout << ans;
}

晚上abc和cf global round25,都止步于D

星期天:

思路:用前缀异或和能优化到n^2,继续优化则考虑拆位

           Sl-1 ^ Sr从二进制位上看,两数在 i 位上相异,就能对答案产生1<< i 的贡献,任意的一个0和一个1就能产生这样的区间,所以按位统计前缀和中的0和1的数量,能在 i 位数上产生sum0*sum1个区间,对答案产生 1<< i * sum0*sum1 的贡献

这里还有一个注意的点,在统计前缀和第 i 位0 1数量时,我使 a【j】 & 1<< i ,这样是错误的,因为如果a【j】第 i 位为0,上式结果还是0,但若为1,上式结果会是1<< i ,即产生了越界,并没有被统计到1的数量里,在布尔式判断对错时,a【j】& 1<< i 和 a【j】>> i & 1 效果是相同的,但这种情况就并非如此了,需注意

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int a[N];
int c[22][2];
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		cin >> a[i];
		a[i]^=a[i-1];
	}
	for(int i=0;i<=20;i++)
		for(int j=0;j<=n;j++)
			c[i][a[j]>>i&1]++;   //a[j]&1<<i结果为1<<i而并非1
	ll ans=0;
	for(int i=0;i<=20;i++)
		ans+=(1ll<<i)*c[i][0]*c[i][1];
	cout << ans;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值