1.x进制减法(贪心)
思路:除了最高位和最底位为固定的,其余的要想满足其差最小,应该为max(2,max(a[i] + 1, b[i]+1)) 这样形成最小值
#include<iostream>
using namespace std;
typedef long long ll;
#define int long long
const int N = 1e5+3, p = 1000000007;
ll a[N], b[N], c[N]; // c[i] 为 第i个位的进制数
int n, m, q;
int main(){
cin >> q >> n;
for(int i=n; i>=1; i--) cin >> a[i];
cin >> m;
for(int i=m; i>=1; i--) cin >> b[i];
for(int i=1; i<=n; i++){
c[i] = max(2ll, max(a[i], b[i]) + 1); // 初始化 c[],并保证c[i] >= 2
}
ll ans = 0;
for(ll i = 1, j = 1; i <= n; i ++ ){ // i 表示位数,j 表示第i位的权值
ans += j * (a[i] - b[i]) % p;
ans = (ans % p + p) % p; // 因为有减法,所以需要放止下溢出
j = 1ll * j * c[i] % p; // 更新权值
}
cout << ans << '\n';
return 0;
}
2.统计子矩阵(双指针优化)
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1010;
ll g[N][N],g2[N][N];
ll n,m,k,ans = 0;
void add(int x,int y)
{
g[x][y] += g[x][y-1];
g[x][y] += g[x-1][y];
g[x][y] -= g[x-1][y-1];
}
int main()
{
cin>>n>>m>>k;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= m ; j++)
{
int t;
cin>>t;
add(i,j);
g[i][j] += t;
}
}
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= m ; j++)
{
for(int t = i ; t <= n ; t++)
{
for(int y = j ; y <= m ; y++)
{
int q = g[t][y] - g[i-1][y] - g[t][j-1] + g[i-1][j-1];
if(q <= k){
ans++;
}
}
}
}
}
cout<<ans<<endl;
}
优化思如:对每一行进行前缀和相加,那么我们会将一行变为一个维度,从而降低时间复杂度
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
typedef long long LL;
int a[N][N];
int main()
{
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= m; j ++ )
scanf("%d", &a[i][j]);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
a[i][j] += a[i][j - 1]; //每一行进行单独前缀和
LL cnt = 0;//统计个数
for(int i = 1; i <= m; i++){ //两列进行维度减少 n^4->n^3
for(int j = i; j <= m; j++){
for(int l = 1, r = 1 , sum = 0 ; r <= n ; r++){ //每行用两个指针进行维护
sum += (a[r][j] - a[r][i-1]); //对两列之间进行每一行相加
while(sum > k && l <= r) //由于从上到下每枚举一行那么该行可以和l到r进行任意组合,最多走2*n(第l到r,第l+1到r,……到r到r)符合一种单调性
{
sum -= (a[l][j] - a[l][i-1]);
l++;
}
cnt += (r - l + 1);
}
}
}
printf("%lld", cnt);
return 0;
}
3.积木画(类蒙的里安的梦想)
f(i,j) = 表示操作完前第i列的状态为j的所有方案的集合
#include<bits/stdc++.h>
using namespace std;
/*
f(i,j) = 表示操作完前第i列的状态为j的所有方案的集合
g(j,k) = 表示操作前i-1列的四种情况,可以使i列形成的情况数
i i+1 f[i][j]对于每一列都会出现4种情况
0 0 0 0 1 1
0 0 0 1 0 1
*/
const int N = 1e7 + 10 , mod = 1e9 + 7;
int n;
int g[4][4] = {
{1,1,1,1},
{0,0,1,1},
{0,1,0,1},
{1,0,0,0},
};
/*
i-1 i i+1
1 0 0
1 0 0
1 0 g[0][0] //当前一列是空的时候,可以这样摆,导致f[i+1][0] += f[i][0] * g[0][0]
1 0 第i+1列全空为第i列全空这样摆可以导致
1 1 g[0][1]
1 0
1 0 g[0][2]
1 1
1 1 g[0][3]
1 1
//其余情况
1 0 g[1][2] //当前一列是上面有一个的情况
0 0 g[1][3]
1 0 g[3][0] //当前一列一列都有的情况
1 0
0 0 g[2][3] //当前一列下面有一个的情况
1 0 g[2][1]
*/
int f[N][4]; //每一列4种情况
int main()
{
scanf("%d",&n);
f[1][0] = 1; //为什么这里f[1][0]是1,因为刚上来时第一列未放入,所以是1
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j < 4 ; j++)
for(int k = 0 ; k < 4 ; k++)
f[i+1][k] = (f[i+1][k] + f[i][j] * g[j][k]) % mod;//上一个是空的,上一个是上面突出一个,下面突出一个,第二列全满
//因为前一列影响下一列,所以我们只统计在前n列全满的情况下,n+1为空即可
cout<<f[n+1][0]<<endl;
return 0;
}
//https://www.acwing.com/problem/content/293/
#include<iostream>
#include<cstring>
using namespace std;
//数据范围1~11
const int N = 12;
//每一列的每一个空格有两种选择,放和不放,所以是2^n
const int M = 1 << N;
//方案数比较大,所以要使用long long 类型
//f[i][j]表示 i-1列的方案数已经确定,从i-1列伸出,并且第i列的状态是j的所有方案数
long long f[N][M];
//第 i-2 列伸到 i-1 列的状态为 k , 是否能成功转移到 第 i-1 列伸到 i 列的状态为 j
//st[j|k]=true 表示能成功转移
bool st[M];
//n行m列
int n, m;
int main() {
// 预处理st数组
while (cin >> n >> m, n || m) {
for (int i = 0; i < 1 << n; i++) {
// 第 i-2 列伸到 i-1 列的状态为 k ,
// 能成功转移到
// 第 i-1 列伸到 i 列的状态为 j
st[i] = true;
// 记录一列中0的个数
int cnt = 0;
for (int j = 0; j < n; j++) {
// 通过位操作,i状态下j行是否放置方格,
// 0就是不放, 1就是放
if (i >> j & 1) {
// 如果放置小方块使得连续的空白格子数成为奇数,
// 这样的状态就是不行的,
if (cnt & 1) {
st[i] = false;
}
}else cnt++;
// 不放置小方格
}
if (cnt & 1) st[i] = false;
}
// 初始化状态数组f
memset(f, 0, sizeof f);
// 棋盘是从第0列开始,没有-1列,所以第0列第0行,不会有延伸出来的小方块
// 没有横着摆放的小方块,所有小方块都是竖着摆放的,这种状态记录为一种方案
f[0][0] = 1; //f[0][0]不存在,所以f[0][0]为第一列前的一种情况,
//f[m][0]因为第m列为最后一列,不能放,放即超界,所以f[m][0]这里用来统计所有情况
// 遍历每一列
for (int i = 1; i <= m; i++) {
// 枚举i列每一种状态
for (int j = 0; j < 1 << n; j++) {
// 枚举i-1列每一种状态
for (int k = 0; k < 1 << n; k++) {
// f[i-1][k] 成功转到 f[i][j]
if ((j & k) == 0 && st[j | k]) { //j|k的意思为 i的j方案和i的k方案重合部分是否相符
f[i][j] += f[i - 1][k]; //那么这种状态下它的方案数等于之前每种k状态数目的和
}
}
}
}
// 棋盘一共有0~m-1列
// f[i][j]表示 前i-1列的方案数已经确定,从i-1列伸出,并且第i列的状态是j的所有方案数
// f[m][0]表示 前m-1列的方案数已经确定,从m-1列伸出,并且第m列的状态是0的所有方案数
// 也就是m列不放小方格,前m-1列已经完全摆放好并且不伸出来的状态
cout << f[m][0] << endl;
}
return 0;
}
4.扫雷(手写哈希)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 50010, M = 999997;
int n, m;
struct Circle
{
int x, y, r;
}cir[N];
LL h[M];
int id[M];
bool st[M];
LL get_key(int x, int y)
{
return x * 1000000001ll + y;
}
int find(int x, int y)
{
LL key = get_key(x, y);
int t = (key % M + M) % M;
while (h[t] != -1 && h[t] != key)
if ( ++ t == M)
t = 0;
return t;
}
int sqr(int x)
{
return x * x;
}
void dfs(int x, int y, int r)
{
st[find(x, y)] = true;
for (int i = x - r; i <= x + r; i ++ )
for (int j = y - r; j <= y + r; j ++ )
if (sqr(i - x) + sqr(j - y) <= sqr(r))
{
int t = find(i, j);
if (id[t] && !st[t])
dfs(i, j, cir[id[t]].r);
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++ )
{
int x, y, r;
scanf("%d%d%d", &x, &y, &r);
cir[i] = {x, y, r};
int t = find(x, y);
if (h[t] == -1) h[t] = get_key(x, y);
if (!id[t] || cir[id[t]].r < r)
id[t] = i;
}
while (m -- )
{
int x, y, r;
scanf("%d%d%d", &x, &y, &r);
for (int i = x - r; i <= x + r; i ++ )
for (int j = y - r; j <= y + r; j ++ )
if (sqr(i - x) + sqr(j - y) <= sqr(r))
{
int t = find(i, j);
if (id[t] && !st[t])
dfs(i, j, cir[id[t]].r);
}
}
int res = 0;
for (int i = 1; i <= n; i ++ )
if (st[find(cir[i].x, cir[i].y)])
res ++ ;
printf("%d\n", res);
return 0;
}
5.李白打酒(dp)
f(i,j,k) = i为店的个数,j为花的个数,k为酒的斗数 两种情况:走店,走酒 最后为花且酒为0, f(i,j-1,k+1) -> f(i,j,k) 最后一步
#include<bits/stdc++.h>
using namespace std;
const int N = 110 , mod = 1e9 + 7;
int n,m;
int f[N][N][N];
int main()
{
f[0][0][2] = 1;
cin>>n>>m;
for(int i = 0 ; i <= n ; i++)//i为店,j为花,k为酒
for(int j = 0 ; j <= m ; j++)
for(int k = 0 ; k <= m ; k++)//k最多有m斗,因为花不够,导致最后无法为0
{
//遇店
if(i && k % 2 == 0) f[i][j][k] = (f[i][j][k] + f[i-1][j][k/2])%mod;
//遇花
if(j) f[i][j][k] = (f[i][j][k] + f[i][j-1][k+1])%mod;
}
cout<<f[n][m-1][1]<<endl;
return 0;
}
6.砍竹子
分析:一个数最多6次便可以运算到1,所以用f(i,j)储存第i个数的第j次形态 我们首先计算所有单个数据几次到1,然后对每一个数值进行看是可以归到一类 ,从而减少
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200010 , M = 10;
ll n,m;
ll f[N][M];//计算该数每一层为几
ll stk[M],res = 0;
int main()
{
scanf("%lld",&n);
for(int i = 1 ; i <= n ; i++)
{
ll x;
scanf("%lld",&x);
ll top = 1;
while(x > 1) stk[top++] = x , x = sqrt(x/2 + 1);
m = max(m,top - 1);
res += top - 1;
for(int j = 1 , k = top - 1 ; k >= 1 ; k--,j++)
{
f[i][j] = stk[k];
}
}
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= m ; j++)
if(f[i][j] && f[i][j] == f[i-1][j])
res--;
printf("%lld",res);
}