1002-Time-division Multiplexing

题意:
给定你\(n\)个长度小于\(12\)的字符串,然后循环的构造一个无限长都的字符串\(s\),构造方式为依次从\(1-n\)的串中取出一个一个字符,如两个字符串长度为\(3,2\),那么\(s\) = \(t_{11}t_{21}t_{12}t_{22}t_{13}t_{21}t_{11}t_{22}t_{12}t_{21}t_{13}t_{22}\ \ \ t_{11}t_{21}\),问你能包含\(n\)个字符串中出现的所有字符的最小长度的子串长度为多少

思路:
会发现经过某个长度后\(s\)就会重复,那么这个长度是多少呢,手写几个例子会发现等于\(n\)个字符串的\(LCM*n\),但是这样考虑还不行,因为串是无限长度,所以需要把串长再乘\(2\)才会得到不遗漏的\(s\)的一个模式串,然后我们就只需要在循环节长度\(*2\)的这个串上做尺取就可以得到我们想要的答案了。

复杂度因为\(lcm(2,3,4,7,8,9,10,11,12) = 27720\),所以串长最多也就是\(5.5e6\),再加上双指针\(O(n)\)的扫一遍,时间正好。

\(hdoj\)恶心坏了,写完了交不上去......

#include <bits/stdc++.h>

using namespace std;

#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 2e7 + 10;
char s[N];
char t[110][20];
int len[110];
int n,cnt[30],tot,vis[30];

int gcd(int a,int b) {
	return b == 0 ? a : gcd(b,a%b);
}

void solve() {
	tot = 0;
	for(int i = 0;i < 26;i ++) vis[i] = cnt[i] = 0;
	scanf("%d",&n);
	getchar();
	for(int i = 1;i <= n;i ++) {
		scanf("%s",t[i]+1);
		len[i] = strlen(t[i]+1);
	}
	for(int i = 1;i <= n;i ++) {
		for(int j = 1;j <= len[i];j ++) {
			if(!vis[t[i][j]-'a']) {
				vis[t[i][j]-'a'] = 1;
				tot++;
			}
		}
	}
	int lcm = len[1];
	for(int i = 2;i <= n;i ++) {
		lcm = lcm * len[i] / gcd(lcm,len[i]);
	}
	lcm *= n;
	int id = 1,round = 1;
	for(int i = 1;i <= lcm;i ++) {
		if(id > n) {
			id = 1; round++;
		}
		s[i] = t[id][(round-1)%len[id]+1];
		id++; 
	}
	for(int i = lcm + 1;i <= lcm * 2;i ++) {
		s[i] = s[i-lcm];
	}
	lcm *= 2;
	s[lcm+1] = '\0';
	int mi = INF;
	int now = 0;
	for(int st = 1,ed = 1;st <= lcm;) {
		if(ed <= lcm && now < tot) {
			while(ed <= lcm && now < tot) {
				if(cnt[s[ed]-'a'] == 0) {
					now++;
				}
				cnt[s[ed]-'a']++;
				ed++;
			}
		}
		if(now == tot) {
			mi = min(mi,ed - st);
		}
		cnt[s[st]-'a']--;
		if(cnt[s[st]-'a'] == 0) now--;
		st++;
	}
	printf("%d\n",mi);
}

int main() {
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
}

1007-Function

题意:
2021CCPC网络选拔赛 (unrated)部分题目_#define

思路:
可以发现\(g(x)_{max} = g(99999) = 54\),所以\(g(x)\)至多\(54\)个不同的值,那么当\(g(x)的值确定之后\),这个函数的图形就是一个二次函数的一些不连续的点(因为不是所有的\(x \ g(x)\) = 当前枚举的值)
所以直接枚举\(g(x)\)的值,然后分类讨论这个二次函数的图像的样子,\(a=0,a<0,a>0\)即可确定函数的最小值。
预处理出来\(g(x)\)=枚举值的\(x\)有哪些,每次找最值只需要在其中二分即可,但是对称轴位置处可能不存在\(x\),所以我们只需要二分第一个\(\ge \ x\)的位置,和它前面的位置去这两个位置处的最小值即可(\(a>0\)的情况)

有一个小细节就是我们预处理的是\(1 - 1000000\)的值,但是每次会给出一个N来固定上限,所以每次对于一个特定的\(N\)来说,要确定一个上界,二分或者直接取边界的操作都不能直接超过这个上界

#include <bits/stdc++.h>

using namespace std;

#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-6;
const int N = 60;
std::vector<ll>vec[N];

ll cal(ll x) {
	ll sum = 0;
	while(x) {
		sum += x % 10;
		x /= 10;
	}
	return sum;
}

void solve() {
	ll ans = INF;
	ll A,B,C,D,N;
	scanf("%lld%lld%lld%lld%lld",&A,&B,&C,&D,&N);
	//f(x) = Ax^2g(x) + Bx^2 + Cxg(x)^2 + Dxg(x)
	//(Ag(x) + B)x^2 + (Cg(x)^2 + Dg(x))x
	for(ll i = 1;i <= 54;i ++) {
		if(sz(vec[i]) == 0 || vec[i][0] > N) continue;
		ll a = A * i + B,b = C * i * i + D * i;
		ll r = upper_bound(vec[i].begin(),vec[i].end(),N) - vec[i].begin();
		//因为预处理的是1-1e6,这是一个wa点
		if(a <= 0) {
			ll j1 = vec[i][0];
			ll j2 = vec[i][r-1];
			ans = min(ans,min(a * j1 * j1 + b * j1,a * j2 * j2 + b * j2));
		}
		else if(a > 0) {
			ll mid = b / (-2 * a);
			std::vector<ll>::iterator j1 = lower_bound(vec[i].begin(),vec[i].begin() + r,mid),j2;
			if(j1 == vec[i].begin() + r) {//处理一个找不到的情况
				j1 = j2 = prev(j1);
			}
			else {
				if(j1 == vec[i].begin()) j2 = j1;
				else j2 = prev(j1);
			}
			ll x1 = *j1,x2 = *j2;
			ans = min(ans,min(a * x2 * x2 + b * x2,a * x1 * x1 + b * x1));
		}
	}
	printf("%lld\n",ans);
}

int main() {
	for(ll i = 1;i <= 1000000;i ++) {
		vec[cal(i)].pb(i);
	}
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
}

1011-Shooting Bricks

题意:
\(n\)\(m\)列的矩形,每个格子的内部都有一个砖块,现在有一把手枪和\(k\)发子弹,一发子弹可以打碎一个砖块,你只能从每一列的最下面一行开始打,每个砖块被击碎后都会有相应的奖励分数\(f_{i,j}\),同时有些砖块被打碎后再奖励分数的基础上还会奖励一发子弹,\(c_{i,j} = Y\)代表打碎后会奖励一发子弹,\(c_{i,j} = N\)代表打碎后不会奖励子弹。
让你求出初始有\(k\)发子弹能获得的最大的分数是多少。

思路:
因为每块砖有\(Y/N\)的两种状态,要考虑最后一颗子弹打到这个砖块的两种不同的情况,\(dp[i][j][0]\)表示\(1-i\)列中,消耗了\(j\)发子弹,且最后一发子弹打在了\(N\)砖块上的情况,\(dp[i][j][1]\)表示\(1-i\)列中,消耗了\(j\)发子弹,且最后一发子弹打在了\(Y\)砖块上的情况。
注意这里用的是消耗这个词,既可以认为打到\(Y\)砖块上时不消耗子弹的。

我们还需要提前处理一个\(w\)数组,\(w[i][j][0/1]\)表示第\(i\)列消耗了\(j\)发子弹,且最后一发子弹打到了\(N/Y\)上。

这里可以把最后一发子弹打到了\(Y/N\)上换一种说法,即当前枚举的前\(i\)列中是否包含了能够打出的最后一发子弹,因为打到了\(Y\)上相当于我们又获得了一枚子弹,打到了\(N\)上就相当于没了。

我们把\(i\)\(1-m\)枚举第\(i\)列,\(j\)\(0-k\)枚举,当前总共花费了多少的子弹,\(nowcol\)\(0-min(j,n)\)枚举当前第\(i\)列消耗了多少发子弹,类似于一个做背包的过程。
\(dp[i][j][1]\),即前\(i\)列均不包含最后一发子弹,那么只能从\(dp[i-1][j-nowcol][1],w[i][nowcol][1]\)这两个状态来转移过来,即前\(i-1\)列不包含最后一发子弹,第i列也不包含。
\(dp[i][j][0]\)则可从两个不同的状态对转移过来,\((dp[i-1][j-nowcol][0],w[i][nowcol][1]),(dp[i-1][j-nowcol][1],w[i][nowcol][1])\)
再加上一些判断小细节,即最后一发子弹转移的状态一定要分配到子弹,因为最后一发子弹一定是会实实在在消耗掉一颗子弹的。

#include <bits/stdc++.h>

using namespace std;

#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 210;

//dp[i][j][0]表示1-i列共消耗了j发子弹且最后一发子弹打到了N砖块上 也就是最后一发子弹在当前列
//dp[i][j][1]表示1-i列共消耗了j发子弹且最后一发子弹打到了Y砖块上 也就是最后一发子弹不在当前列

//w[i][j][0]表示第i列消耗了了j发子弹且最后一发子弹打到了N砖块上能获得的最大的价值
//w[i][j][1]表示第i列消耗了了j发子弹且最后一发子弹打到了Y砖块上能获得的最大的价值

int n,m,k;
char ch;
int dp[N][N][2],w[N][N][2],a[N][N],can[N][N];

void solve() {
	scanf("%d%d%d",&n,&m,&k);
	for(int i = 1;i <= n;i ++) {
		for(int j = 1;j <= m;j ++) {
			scanf("%d %c",&a[i][j],&ch);
			if(ch == 'Y') can[i][j] = 1;
		}
	}
	for(int i = 1;i <= m;i ++) {
		int used = 0;
		for(int j = n;j >= 1;j --) {
			if(can[j][i] == 1) {
				w[i][used][1] += a[j][i];
			}
			else {
				++used;
				w[i][used][1] = w[i][used-1][1] + a[j][i];
				w[i][used][0] = w[i][used-1][1] + a[j][i];
			}
		}
	}
	for(int i = 1;i <= m;i ++) {
		for(int j = 0;j <= k;j ++) {
			for(int nowcol = 0;nowcol <= min(n,j);nowcol ++) {
				dp[i][j][1] = max(dp[i][j][1],dp[i-1][j-nowcol][1] + w[i][nowcol][1]);
				if(j - nowcol > 0) 
					dp[i][j][0] = max(dp[i][j][0],dp[i-1][j-nowcol][0] + w[i][nowcol][1]);
				if(nowcol > 0)
					dp[i][j][0] = max(dp[i][j][0],dp[i-1][j-nowcol][1] + w[i][nowcol][0]);
			}
		}
	}
	printf("%d\n",dp[m][k][0]);
	for(int i = 1;i <= n;i ++) {
		for(int j = 1;j <= m;j ++) {
			can[i][j] = 0;
		}
	}
	for(int i = 1;i <= m;i ++) {
		for(int j = 0;j <= k;j ++) {
			dp[i][j][0] = dp[i][j][1] = w[i][j][0] = w[i][j][1] = 0;
		}
	}
}

int main() {
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
}