题意:一个形如 A , A + 1 , . . . , N , 1 , 2 , . . . , N , 1 , 2 , . . . , N , . . . A, A + 1, ..., N, 1, 2, ..., N, 1, 2, ..., N, ... A,A+1,...,N,1,2,...,N,1,2,...,N,... 的序列,问第 K K K 个数字是什么
对于 A = 1 A = 1 A=1 的情况其实很好解决,就是 ( K − 1 ) m o d N + 1 (K - 1) \bmod N + 1 (K−1)modN+1
实际上其他情况就是加了一个偏移量 A A A,所以答案就是 ( ( k − 1 ) m o d n + a − 1 ) m o d n + 1 ((k - 1)\bmod n + a - 1)\bmod n + 1 ((k−1)modn+a−1)modn+1
#include
using namespace std;
int n, k, a;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k >> a;
cout << ((k - 1) % n + a - 1) % n + 1 << endl;
return 0;
}
或者特判下 k ≤ n − a + 1 k \leq n - a + 1 k≤n−a+1 的情况
#include
using namespace std;
int n, k, a;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k >> a;
if (k <= n - a + 1) cout << k + a - 1 << '\n';
else {
cout << (k - (n - a + 1) - 1) % n + 1 << '\n';
}
return 0;
}
题意:给定 N N N 组询问,每个询问一个 S S S,判断是否存在两个正整数 a , b a, b a,b,满足 4 a b + 3 a + 3 b = S 4ab + 3a + 3b = S 4ab+3a+3b=S
我们可以预处理出所有的所有可达的 S S S 值,记录在一个 s e t set set 中,之后每次查询只需要查 s e t set set 中有没有这个值即可
因为 1 ≤ S i ≤ 1000 1\leq S_i\leq 1000 1≤Si≤1000,所以 a , b a, b a,b 的范围最大是 1 ≤ a , b ≤ 1000 1\leq a, b\leq 1000 1≤a,b≤1000,这样枚举所有的可能的 ( a , b ) (a, b) (a,b) 算出 S S S 值即可
#include
using namespace std;
set st;
int n, x, ans;
int main(void) {
for (int a = 1; a <= 1000; a++) {
for (int b = 1; b <= 1000; b++) {
st.insert(4 * a * b + 3 * a + 3 * b);
}
}
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> x;
ans += !st.count(x);
}
cout << ans << endl;
return 0;
}
题意:给定 N N N,判断存在多少组正整数 ( A , B , C ) , A ≤ B ≤ C (A, B, C), A\leq B\leq C (A,B,C),A≤B≤C,满足 A B C ≤ N ABC\leq N ABC≤N
先考虑枚举哪一个数
没显然我们枚举 A A A
对于一个 A A A 来说,有多少个 B C ≤ ⌊ N A ⌋ BC\leq \lfloor \frac{N}{A} \rfloor BC≤⌊AN⌋ 呢?
我们再 O ( ⌊ N A ⌋ ) O(\sqrt{\lfloor\frac{N}{A}\rfloor}) O(⌊AN⌋) 枚举 B B B
下面只需要判断有多少个 C C C 在 [ B , ⌊ N A B ⌋ ] [B, \lfloor \frac{N}{AB} \rfloor] [B,⌊ABN⌋] 中即可
答案为 ⌊ N A B ⌋ − B + 1 \lfloor \frac{N}{AB} \rfloor - B + 1 ⌊ABN⌋−B+1
时间复杂度: O ( N 2 3 ) O(N^{\frac{2}{3}}) O(N32)
题意: N N N 个部门,第 i i i 个部门有 A i A_i Ai 个雇员,完成一个项目需要 K K K 个不同部门的人,问最多能完成多少个项目?
直接做发现不太好做,贪心取大的话复杂度不对,所以考虑从答案入手
二分答案
这可问题可以看成:有 X X X 个项目,每个部门向尽可能多的项目里去分配人员(这无论是对项目还是对于该部门来说都是最优的),最后判断这个方案可不可行即可
需要注意二分上界不能取太大(比如 1 0 18 10^{18} 1018),会在计算的过程中爆 l o n g l o n g long\ long long long
时间复杂度: O ( N log ( max A i ) ) O(N\log(\max A_i)) O(Nlog(maxAi))
#include
using namespace std;
typedef long long ll;
#define int ll
const int N = 2e5 + 5;
int n, k, a[N];
bool check(int now) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += min(now, a[i]);
}
return sum >= k * now;
}
signed main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int l = 0, r = 2e17 / k, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
l = mid + 1;
}
else r = mid - 1;
}
cout << ans << endl;
return 0;
}
题意:给定一个只含有 K , E , Y K, E, Y K,E,Y 字符的字符串,计算在最多 K K K 次两两相邻位置交换后形成的所有可能的字符串集合的大小
首先先考虑这样的一个问题:给定两个同构串 S , T S, T S,T,他们之间的最小转移距离是多少?
那肯定是贪心地从左向右匹配,找最近的换,比如:
那么对于此题,一个很简单的想法是:枚举所有可能的串,计算两者之间的距离,如果这个距离 d ≤ K d\leq K d≤K ,那么对答案产生贡献,否则忽略
但这样做的复杂度大约是 O ( 3 30 ) O(3^{30}) O(330) 的,显然不可行,所以我们可以尝试用线性 d p dp dp 来做
d p [ i ] [ j ] [ k ] [ v ] dp[i][j][k][v] dp[i][j][k][v] 表示使得 S S S 的前 i i i 个位置通过 j j j 次移动,含有 k k k 个 K K K, v v v 个 E E E, i − k − v i - k - v i−k−v 个 Y Y Y 的所有可能的字符串种类数
这个动态规划的核心点在于,我已经知道了前 i i i 个字符里分别所含的 K , E , Y K, E, Y K,E,Y 的个数,那么剩下的字符串的状态我也知道了,因为前面都是贪心移动的,比如原来的字符串为 S = Y K E E S = YKEE S=YKEE,我当前知道前 2 2 2 个含有 1 1 1 个 K K K, 1 1 1 个 E E E ,那么剩下的一定是 Y E YE YE 而不可能是 E Y EY EY
基于这个想法,我们的 d p dp dp 就变得很简单了
或许正纠结怎么优化,不用考虑这个问题,just do it!这 ∣ S ∣ ≤ 30 |S|\leq 30 ∣S∣≤30,大胆转移就行
转移为:
n x t k nxt_k nxtk 表示下一个字符 K K K 距离当前位置的距离, n x t e , n x t y nxt_e, nxt_y nxte,nxty 同理,具体细节见代码
时间复杂度: O ( ∣ S ∣ 6 ) O(|S|^6) O(∣S∣6) 常数很小
也可以预处理出给定前 i i i 个位置的 K , E , Y K,E, Y K,E,Y 数量得到的剩下字符串,使得 d p dp dp 最内层复杂度由 O ( ∣ S ∣ ) O(|S|) O(∣S∣) 变为 O ( 1 ) O(1) O(1),从而使得总复杂度变为 O ( ∣ S ∣ 5 ) O(|S|^5) O(∣S∣5)
#include
using namespace std;
typedef long long ll;
#define int ll
const int N = 33;
char s[N];
int dp[N][N * N][N][N], k, n, pos, cnt, m;
signed main(void) {
scanf("%s%d", s + 1, &m);
n = strlen(s + 1);
dp[0][0][0][0] = 1;
int ck = 0, ce = 0, cy = 0;
for (int i = 1; i <= n; i++) {
ck += s[i] == 'K';
ce += s[i] == 'E';
cy += s[i] == 'Y';
}
for (int i = 0; i < n; i++) {
for (int x = 0; x <= n * n; x++) {
for (int j = 0; j <= ck; j++) {
for (int k = 0; k <= ce; k++) {
if (i - j - k > cy || j + k > i) continue;
int cntk = j, cnte = k, cnty = i - j - k;
string p; p.clear();
for (int v = 1; v <= n; v++) {
if (s[v] == 'K') {
if (cntk) cntk--;
else p += 'K';
}
else if (s[v] == 'E') {
if (cnte) cnte--;
else p += 'E';
}
else {
if (cnty) cnty--;
else p += 'Y';
}
}
int nxtk = -1, nxte = -1, nxty = -1;
for (int v = 0; v < (int) p.size(); v++) {
if (p[v] == 'K') {
nxtk = v; break;
}
}
for (int v = 0; v < (int) p.size(); v++) {
if (p[v] == 'E') {
nxte = v; break;
}
}
for (int v = 0; v < (int) p.size(); v++) {
if (p[v] == 'Y') {
nxty = v; break;
}
}
if (nxtk != -1) dp[i + 1][x + nxtk][j + 1][k] += dp[i][x][j][k];
if (nxte != -1) dp[i + 1][x + nxte][j][k + 1] += dp[i][x][j][k];
if (nxty != -1) dp[i + 1][x + nxty][j][k] += dp[i][x][j][k];
}
}
}
}
int ans = 0;
for (int i = 0; i <= min(m, n * n); i++) {
for (int j = 0; j <= n; j++) {
for (int k = 0; k <= n; k++) {
ans += dp[n][i][j][k];
}
}
}
cout << ans << endl;
return 0;
}
题意:给一个 H × W H\times W H×W 的矩阵 A A A, 1 ≤ A i , j ≤ 1 0 9 , 1 ≤ H , W ≤ 30 1\le A_{i,j}\le 10^9,1\le H,W\le 30 1≤Ai,j≤109,1≤H,W≤30,
从左上角 ( 1 , 1 ) (1,1) (1,1) 出发,只可以向下、向右走,要求抵达右下角 ( H , W ) (H,W) (H,W)
计算途经前 K K K 大 A i , j A_{i,j} Ai,j 之和为 s u m sum sum。 1 ≤ K < H + W 1\le K< H+W 1≤K<H+W ,要求求出 s u m sum sum 的最小值
方案为 d p dp dp,很容易想到的 d p dp dp状态为 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示:抵达到 ( i , j ) (i,j) (i,j)时的
路径前 k k k大和的最小值。
但是我们发现,无法顺利地转移方程。因为当我们试图用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]更新 d p [ i + 1 ] [ j ] [ k ] dp[i+1][j][k] dp[i+1][j][k]时,
我们要在我们已经走过的路径中再添上 A i + 1 , j A_{i+1,j} Ai+1,j,从而导致路径的前 k k k大和发生了变换,
这要求我们记录所有路径途径的值。
换而言之,我们的 d p dp dp方案具有后效性!
为了解决 d p dp dp方案的后效性,我们可以采取如此策略
枚举分界值 x x x,我们认为最终方案选取的前 K K K大的 A i , j A_{i,j} Ai,j的最小值即为 x x x
那么,在我们转移状态的过程中只要碰到数值 ≥ x \ge x ≥x,我们就统计入和,并且由 k k k更新到 k + 1 k+1 k+1
需要注意的是,当 A i , j = x A_{i,j}=x Ai,j=x时,有统计与不统计两种情况
#include
using namespace std;
typedef long long ll;
#define rep(i,n) for(int i = 0; i < n; i++)
void chmin(ll&a,ll b){if(a>b)a=b;}
const ll inf = 1001001001001001001;
int main(){
int h, w, K; cin >> h >> w >> K;
vector> grid(h, vector(w));
rep(i, h) rep(j, w) cin >> grid[i][j];
ll ans = inf;
for(auto y : grid) for(auto x : y) {
vector>> dp(K + 1, vector>(h, vector(w, inf)));
if(grid[0][0] >= x) dp[1][0][0] = grid[0][0];
if(grid[0][0] <= x) dp[0][0][0] = 0;
rep(i, K + 1) rep(j, h) rep(k, w) {
if(j != h - 1){
if(i != K && grid[j + 1][k] >= x) chmin(dp[i + 1][j + 1][k], dp[i][j][k] + grid[j + 1][k]);
if(grid[j + 1][k] <= x) chmin(dp[i][j + 1][k], dp[i][j][k]);
}
if(k != w - 1){
if(i != K && grid[j][k + 1] >= x) chmin(dp[i + 1][j][k + 1], dp[i][j][k] + grid[j][k + 1]);
if(grid[j][k + 1] <= x) chmin(dp[i][j][k + 1], dp[i][j][k]);
}
}
chmin(ans, dp[K][h - 1][w - 1]);
}
cout << ans << endl;
return 0;
}