K77的校园生活(2025年)

K77的校园生活

2025.9.2

cf 2030 D. QED's Favorite Permutation

思路

每个点需要交换的位置是固定不变的 只有可操作的L和R在改变 用need[i]记录每个点i是否需要与点i+1交换

初始化need当\(i\not=a[i]\) \(need[min(i,a[i])]\)到\(need[max(i,a[i])-1]\)都+1 最终need[i]!=0的就是要换的

用set记录哪些点需要交换但是换不了 在每次修改x时 只用检查x的左右两边 即O(1)处理操作

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
typedef long long ll;
int n,q,a[maxn];
char s[maxn];
int pos[maxn];
bool ok(int a,int b) {
	return s[a]=='R'||s[b]=='L';
}
int need[maxn];
void solve() {
	cin>>n>>q;
	for (int i=1;i<=n;i++) need[i]=0;
	
	for (int i=1;i<=n;i++) cin>>a[i];
	for (int i=1;i<=n;i++) cin>>s[i];
	for (int i=1;i<=n;i++) {
		if (a[i]==i) continue;
		int u=i,v=a[i];
		if (u>v) swap(u,v);
		need[u]++,need[v]--;
	}
	for (int i=1;i<=n;i++) need[i]+=need[i-1];
	set<int> se;
	for (int i=1;i<=n;i++) {
		if (need[i]&&!ok(i,i+1)) se.insert(i);
	}
	
	while (q--) {
		int x;
		cin>>x;
		
		if (need[x-1]) {
			if (!ok(x-1,x)) se.erase(x-1);
			if (ok(x-1,x)&&s[x-1]=='L') se.insert(x-1);
		}
		if (need[x]) {
			if (!ok(x,x+1)) se.erase(x);
			if (ok(x,x+1)&&s[x+1]=='R') se.insert(x);
		}
		
		if (s[x]=='L') s[x]='R';
		else s[x]='L';
		
		cout<<(se.empty()?"Yes\n":"No\n");
		
	}
}

int main() {
	int t=1;
	cin>>t;
	while (t--) solve();
}

2025.9.3

cf 2005C. Lazy Narek

思路

求最值考虑贪心 但是这个题贪不了 所以dp一下

发现这个题有点像用dp[i][j]表示前i个元素的答案 并且现在的状态为j

所以用j表示当前要组成narek差了哪个字母

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
typedef long long ll;
typedef pair<int,int> pii;
string t="narek";
string a[2000];
int dp[maxn][10];
int n,m;

pii ask(string s,int now) {
	int ans=0;
	for (int i=0;i<m;i++) {
		if (s[i]==t[now]) {
			now++;
			if (now==5) {
				ans+=10;
				now=0;
			}
		}
		if (t.find(s[i])!=string::npos) ans--;
	}
	return {ans,now};
} 

void solve() {
	cin>>n>>m;
	for (int i=1;i<=n;i++) cin>>a[i];
	
	for (int i=0;i<=n;i++) {
		for (int j=0;j<5;j++) dp[i][j]=-2e9;
	}
	dp[0][0]=0;
	
	for (int i=1;i<=n;i++) {
		
		for (int j=0;j<5;j++) {
			dp[i][j]=max(dp[i-1][j],dp[i][j]);
			pii temp=ask(a[i],j);
			int now=temp.second,g=temp.first;
			dp[i][now]=max(dp[i][now],dp[i-1][j]+g);
		}
	}
	int ans=0;
	for (int i=0;i<5;i++) ans=max(dp[n][i],ans);
	cout<<ans<<endl;
}

int main() {
	int t=1;
	cin>>t;
	while (t--) solve();
}



cf 1987 D. World is Mine

cf 1987 E. Wonderful Tree!

2025.9.9

cf 1969 C. Minimizing the Sum

思路

看到题目的数据范围 2e5 和 10 可以想到O(nm)的时间复杂度算法

多维dp

dp[i][j]表示到第i个数用了j次操作的最小值

在转移时 枚举k:dp[i][j]从dp[i-1-k][j-k]转移将i~i-k-1变成最小

dp[i][j]取其最小值

dp[n][m]即为答案

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;

ll a[maxn];
ll n,m;
ll dp[maxn][15];
void solve() {
	cin>>n>>m;
	for (int i=1;i<=n;i++) cin>>a[i];
	for (int i=1;i<=n;i++) {
		for (int j=0;j<=m;j++) dp[i][j]=1e18;
	}	
	for (int i=1;i<=n;i++) {
		for (int j=0;j<=m;j++) {
			dp[i][j]=dp[i-1][j]+a[i];
			ll mn=a[i],sum=a[i];
			for (int k=1;k<=j&&i-k>=1;k++) {
				sum+=a[i-k],mn=min(mn,a[i-k]);
				dp[i][j]=min(dp[i][j],dp[i-k-1][j-k]+mn*(k+1));
			}
		}
	}
	cout<<dp[n][m]<<endl;
}
int main() {
	ll t=1;
	cin>>t;
	while (t--) solve();
}

cf 1969 D. Shop Game

思路

题意:alice是老板娘 可以在n个商品中选择进货 其中\(a_i\)是进价 \(b_i\)是售价 bob要买下整个小卖部 他可以直接免费拿k个商品 求alice的最大利润

按售价从大到小排序

枚举i作为分界线 i左边的选k个进价小的进货(用来打发bob) i右边的选不会亏的进货

用一个优先队列q维护打法bob的商品进价

q初始为前k个进价

如果当前进价比q中最大的进价小 说明这个进价在q中所以将这个进价从q中删去 将下一个进价加入q

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
struct nd{
	ll a,b;
};
nd a[maxn];
ll suf[maxn];
ll n,m; 
void solve() {
	cin>>n>>m;
	for (int i=1;i<=n;i++) cin>>a[i].a;
	for (int i=1;i<=n;i++) cin>>a[i].b;
	priority_queue<ll> q;
	sort(a+1,a+1+n,[](nd x,nd y){
		return x.b>y.b;
	});
	suf[n+1]=0;
	for (int i=n;i>=1;i--) {
		suf[i]=suf[i+1]+max(a[i].b-a[i].a,0ll);
	}
	ll now=0;
	for (int i=1;i<=m;i++) q.push(a[i].a),now+=a[i].a;
	ll ans=0;
	for (int i=m+1;i<=n;i++) {
		ans=max(ans,suf[i]-now);
		if (!q.empty()&&a[i].a<q.top()) {
			now-=q.top();
			now+=a[i].a;
			q.pop();
			q.push(a[i].a);
		}
	}
	cout<<ans<<endl;	
}
int main() {
	ll t=1;
	cin>>t;
	while (t--) solve();
}

2025.9.22

cf 2056 D. Unique Median

思路

答案为 所有可能\(-\)不符合要求的

当子数组为奇数时一定符合要求

观察到\(a_i<=10\)所以考虑 O(\(n·a_i\))

枚举{\(x|1<=x<=10\)} x为子数组中位数 定义数组b 当\(a_i<=x\) 时 \(b_i=-1\) 反之为 \(1\)

对b进行前缀和

当区间\([l,r]\)和为0时 此区间不符合要求 即\([l,r]\)两个中位数{\(u!=v\)}

要使u!=v 得先满足[ l , r ] 之间有x

所以枚举\(r\) 当\(a_r=x\) 时 讲[l,r-1] 之间的每个前缀和出现次数+1

区间\([l,r]\) 间不符合要求的数即为\(pre_r\)的出现次数

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
typedef long long ll;
const int mod=998244353;
typedef pair<int,int> pii;
typedef tuple<ll,ll,ll> tlll;

ll n;
int a[maxn];
int pre[maxn];
ll ans=0;
void k77(int x) {
	for (int i=1;i<=n;i++) {
		pre[i]=pre[i-1]+(a[i]<=x?-1:1);
	}
	
	map<int,int> mp;
	int l=0;
	for (int r=1;r<=n;r++) {
		if (a[r]==x) {
			while (l<r) mp[pre[l++]]++;
		}
		ans-=mp[pre[r]];
	}
	
}

void solve() {
	cin>>n;
	for (int i=1;i<=n;i++) cin>>a[i];
	ans=(n+1)*n/2;
	for (int i=1;i<=10;i++) {
		k77(i); 
	}
	cout<<ans<<endl;
	
	
}

int main() {
	int t=1;
	cin>>t;
	while (t--) solve();
}

cf 2061 F1. Kevin and Binary String (Easy Version)

思路

因为是要使s和t匹配 所以从一边开始匹配

如果s和t最右边的块可以匹配 则t最右边的块中数量减去s最右边的块数量

如果不行 则操作一次并且答案加一 并记录不匹配的数量 在t最右边的块匹配完后 再添加入s最右边

如果不能刚好匹配玩t最右边的块 则输出-1 表示不可能使s和t匹配

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
typedef long long ll;
const int mod=998244353;
typedef pair<int,int> pii;
typedef tuple<ll,ll,ll> tlll;


void solve() {
	vector<pii> a,b;
	string s,t;
	cin>>s>>t;
	int n=s.size();
	s+='.';
	t+='.';
	int cnt=0;
	for (int i=0;i<n;i++) {
		cnt++;
		if (s[i]!=s[i+1]) a.push_back({s[i],cnt}),cnt=0;
	}
	for (int i=0;i<n;i++) {
		cnt++;
		if (t[i]!=t[i+1]) b.push_back({t[i],cnt}),cnt=0;
	}
	int suf=0,ans=0;
	while (!b.empty()) {
		char c=b.back().first;
		int num=b.back().second;
		while (!a.empty()&&num>0) {
			if (a.back().first==c) num-=a.back().second;
			else suf+=a.back().second,ans++;
			a.pop_back();
		}
		if (num!=0) {
			cout<<-1<<endl;
			return ;
		}
		if (suf) {
			if (a.empty()) a.push_back({c^1,suf});
			else {
				if (a.back().first==(c^1)) a.back().second+=suf;
				else a.push_back({c^1,suf});
			}
			suf=0;
		}
		b.pop_back();
	}
	cout<<ans<<endl;
}

int main() {
	int t=1;
	cin>>t;
	while (t--) solve();
}

cf 2126 G1. Big Wins! (easy version)

思路

利用值域约束(a_i ≤100)缩小枚举范围

用辅助数组 b 将中位数条件转化为子数组和的约束

前缀最小值与后缀最大值快速判断有效子数组 避免暴力枚举

验证是否存在一个子数组 [l..r],满足 l ≤ i ≤ r 且 pre[r] - pre[l-1] ≥ 1(即满足中位数条件)。

  1. med 是该子数组的中位数:子数组中 ≥ med 的元素个数 ≥ ⌈k/2⌉(k 是子数组长度),转化为前缀和条件是 pre[r] - pre[l-1] ≥ 1(pre 是辅助数组 b 的前缀和,b[i] 对 ≥ med 的元素记为 1,< med 的记为 -1)。
  2. a[i] 是该子数组的最小值:子数组必须包含位置 i(因为 a[i] 是最小值,子数组中所有元素 ≥ a[i]),且 a[i] 是子数组中最小的元素。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
typedef long long ll;
const int mod=998244353;
typedef pair<int,int> pii;
typedef tuple<ll,ll,ll> tlll;

int n,m,a[maxn],b[maxn],pre[maxn],premn[maxn],sufmx[maxn];

void solve() {
	cin>>n;
	for (int i=1;i<=n;i++) cin>>a[i];
	int ans=0;
	for (int med=1;med<=100;med++) {
		for (int i=1;i<=n;i++) {
			if (a[i]>=med) pre[i]=pre[i-1]+1;
			else pre[i]=pre[i-1]-1;
		}
		for (int i=1;i<=n;i++) premn[i]=min(premn[i-1],pre[i]);
		sufmx[n]=pre[n];
		for (int i=n-1;i>=1;i--) sufmx[i]=max(sufmx[i+1],pre[i]);
		for (int i=1;i<=n;i++) {
			if (premn[i-1]<=sufmx[i]) ans=max(ans,med-a[i]);
		}
	}
	cout<<ans<<endl;
}

int main() {
	int t=1;
	cin>>t;
	while (t--) solve();
}

2025.9.23

cf 2049 D. Shift + Esc

思路

题目长得就是dp

观察数据范围 定义\(dp[i][j][k]\) 表示到达i行j列时i行操作了k次的最下花费

\(dp[i][j][k]\)可以从\(dp[i][j-1][k]\)直接转移

也可以从\(dp[i-1][j][l],l\in{[0,m-1]}\)的最小值转移

这样时间复杂度是\(O(n^4)\)

用一个\(ans[i][j]\)表示\(dp[i][j][k]\)的最小值

在每次计算完\(dp[i][j][k]\)时更新一下\(ans[i][j]\)

就可以把时间复杂度降到\(O(n^3)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
typedef long long ll;
const int N=205;
ll n,m,p;
ll a[N][N];
ll dp[N][N][N];
//dp[i][j][k] 表示到达i行j列时i行操作了k次的最下花费 
ll ans[N][N];
void solve() {
	cin>>n>>m>>p;
	for (int i=1;i<=n;i++) {
		for (int j=1;j<=m;j++) cin>>a[i][j];
	}
	
	for (int i=0;i<=n;i++) {
		for (int j=0;j<=m;j++) {
			for (int k=0;k<=m;k++) dp[i][j][k]=2e18,ans[i][j]=2e18;
		}
	}
	
	for (int k=0;k<m;k++) {
		dp[1][1][k]=a[1][(1+k-1)%m+1]+p*k;
		ans[1][1]=min(ans[1][1],dp[1][1][k]);
	}
	
	for (int i=1;i<=n;i++) {
		for (int j=1;j<=m;j++) {
			for (int k=0;k<m;k++) {
				
				if (i==1&&j==1) continue;
				dp[i][j][k]=min(dp[i][j-1][k]+a[i][(j+k-1)%m+1],
				ans[i-1][j]+a[i][(j+k-1)%m+1]+k*p);
				ans[i][j]=min(ans[i][j],dp[i][j][k]);
			}
		}
	}
	cout<<ans[n][m]<<endl;
}

int main() {
	int t=1;
	cin>>t;
	while (t--) solve();
}

2025.9.24

最长上升子序列(O(nlogn))

思路

1 2 4

1 2 5

在这两个上升序列中第一个是更优的 因为它最后一个元素跟小 后面可以接的元素更多

所以在数组a中

长为x的上升子序列最后一个元素一定比长为x-1的上升子序列最后一个元素大

用\(dp[i]\)表示长为i的上升子序列最后一个元素最小是多少

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
typedef long long ll;
int n;
int a[maxn];
int dp[maxn];
void solve() {
	cin>>n;
	for (int i=1;i<=n;i++) cin>>a[i];
	int ans=0;
	for (int i=1;i<=n;i++) dp[i]=1e9;
	for (int i=1;i<=n;i++) {
		int p=lower_bound(dp+1,dp+1+i,a[i])-dp;
		dp[p]=a[i];
		ans=max(ans,p);
	}
	cout<<ans<<endl;a
}
int main() {
	int t=1;
//	cin>>t;
	while (t--) solve();
}
posted @ 2026-08-20 19:51  acceptedK77  阅读(7)  评论(0)    收藏  举报