T1
题目大意:
翻纸牌, 使得上面至少有一半数字是一样的, 求最少反动次数
简单分析:
模拟, 没啥好说的
标算:
注意需要离散化, Impossible情况判断
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 500010;
const int inf = 1e9 + 7;
int n, top[N*2], bot[N*2], p;
struct hh {int val, st;}a[N*2];
bool cmp_val(hh a, hh b) { return a.val < b.val;}
bool cmp_st(hh a, hh b) { return a.st < b.st;}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &a[i*2-1].val, &a[i*2].val);
a[i*2-1].st = i*2-1;
a[i*2].st = i*2;
}
sort(a+1, a+n*2+1, cmp_val); //离散化
int lst = -1;
for(int i = 1; i <= n*2; ++i) {
p += (lst != a[i].val);
lst = a[i].val;
a[i].val = p;
}
sort(a+1, a+n*2+1, cmp_st); //回归原本的顺序
for(int i = 1; i <= n; ++i) {
int x = a[i*2-1].val, y = a[i*2].val;
++top[x]; //桶计上面的数字
if(x != y) ++bot[y]; //上下一样的不用处理, 桶计下面的数字
}
int hf = (n+1)/2;
int ans = inf;
for(int i = 1; i <= p; ++i) { //挨个儿试
if(top[i] + bot[i] < hf) continue;
if(top[i] >= hf) {
ans = 0; break;
}
if(hf - top[i] < ans) ans = hf - top[i];
}
if(ans == inf) printf("Impossible\n");
else printf("%d\n", ans);
return 0;
}
T2
题目大意:
挂着后缀数组之名的papertiger
字符串按字典序大小排序, 求最小交换次数
简单分析:
字符串hash + 归并排序求逆序对(树状数组也可)
标算:
当时卡在了如何对hash后的字符串比较大小上.
可以二分两个字符串从头到尾哪一位开始不同的, 然后拆开比较即可
RK hash函数: hash = (x(k)*e^0+x(k-1)*e^1+x(k-2)*e^2+…+x1*e^(k-1)) % mo
拆分:hash[i,j] = (hash[j]-hash[i-1]*e^(j-i+1)%mo+mo) % mo
#include<cstdio>
const int N = 50010;
const int base = 29; //底数
const int mod = 1e9 + 7; //模数
int f[N], s[N], tmp[N], n, m, ans;
long long hash[N], pow[N];
char ch[N];
bool lessThanOrEqual(int i, int j) { //二分不同点比较两个字符串字典序
if(i == j) return true;
int l = 0, r = m+1, mi;
long long hsi, hsj;
if(n-j+2 < r) r = n-j+2;
if(n-i+2 < r) r = n-i+2;
while(l+1 < r) {
mi = (l+r)>>1;
hsi = hash[i+mi-1] - hash[i-1]*pow[mi]%mod;
if(hsi < 0) hsi += mod;
hsj = hash[j+mi-1] - hash[j-1]*pow[mi]%mod;
if(hsj < 0) hsj += mod;
if(hsi == hsj) l = mi;
else r = mi;
}
if(l == m) return true; //两字符串相同
else return s[i+l] < s[j+l];
}
void merge_sort(int l, int r) { //归并排序求逆序对
if(l == r) return;
int mid = (l+r)>>1;
merge_sort(l, mid); merge_sort(mid+1, r);
int i = l, j = mid+1, nt = l;
while(i <= mid || j <= r) {
bool ilej;
if(i > mid) ilej = false;
else if(j > r) ilej = true;
else ilej = lessThanOrEqual(f[i], f[j]);
if(ilej) tmp[nt++] = f[i++];
else {
tmp[nt++] = f[j++];
ans += mid-i+1;
}
}
for(int i = l; i <= r; ++i) f[i] = tmp[i];
}
int main() {
scanf("%d%d", &n, &m);
hash[0] = 0; pow[0] = 1;
scanf("%s", ch+1);
for(int i = 1; i <= n; ++i) {
s[i] = ch[i] - 'a' + 1;
hash[i] = (hash[i-1]*base + s[i]) % mod;
pow[i] = pow[i-1]*base%mod; // 记录每一位的base的倍数
f[i] = i;
}
s[n+1] = 0;
merge_sort(1, n);
printf("%d\n", ans);
return 0;
}
T3
不可做的题目大意:
切巧克力(巧克力:qwq), 一块巧克力的重量等于它包含的所有格子的重量之和, 求是否有一种方案可以满足给定最终重量
用命分析:
看起来可以爆搜的样子数据点蛮小的样子…(事实证明不可行, 解答树比想象的大的多)
咋都看不懂的标算:
测试点1、3、5、7、9
wi,j=1
小数据搜索,大数据状态压缩DP+记忆化搜索(显然, 我还不熟状压dp)
F[i][j][sta]表示是否能用sta中的巧克力拼出i*j的矩形
最终答案为F[n][m][2^k-1]
转移时枚举横切/竖切, 以竖切为例
再枚举sta’, 表示sta’中的巧克力被分到左边, 其余被分到右边
根据sta’中的巧克力面积和可以算出左边应该切多长(k)
F[i][j][sta] |= (F[i][k][sta’] && F[i][j-k][sta-sta’])
j那维在存储时可以省去
(其实我是没看懂题解的(或许曾经看懂过,但是现在是看不懂了))
测试点2、4、6、8、10
小数据搜索,大数据状态压缩DP+记忆化搜索
F[i1][i2][j1][j2][sta]表示从子矩形(i1,j1)-(i2,j2)中能否刚好切出sta中的巧克力
最终答案为F[1][n][1][m][2^k-1]
转移时枚举横切/竖切,以竖切为例
再枚举sta’,表示sta’中的巧克力被分到左边,其余分到右边
根据sta’中的巧克力面积和可以算出左边应该切多长(k),或者无法切
怎么算?二分或枚举
转移类似
j2那维在存储时可以省去
#include <cstdio>
#include <list>
#define MAXK 15
#define N 11
struct Quad
{
int a, b, c, d;
Quad(int _a, int _b, int _c, int _d): a(_a), b(_b), c(_c), d(_d) {}
};
std::list<Quad> lf, lfx, lfy;
char f[N][N][N][1<<MAXK],fx[N][N][N][1010],fy[N][N][N][1010];
int sumx[N][N][N],sumy[N][N][N],suma[1<<MAXK],bg[1<<MAXK],ed[1<<MAXK],c[15000000],e[MAXK+1],a[20],
n,m,K,i,j,l,r,T,sta,nc,w;
/*
求:以j1为左边界、j2为右边界、i1为上边界的矩形中,下边界为多少的矩形
重量和是w。如果不存在则返回-1
用二分求
*/
int calcx(int j1, int j2, int i1, int w)
{
// fx[j1][j2][i1][w]用于记录该子问题有没有被求结果
// 已求结果则直接返回结果
if (fx[j1][j2][i1][w] != 0) return fx[j1][j2][i1][w];
// 未求结果,将该状态加入待清空队列
lfx.push_back(Quad(j1,j2,i1,w));
// 二分求i2的位置
int l, r, k;
l = i1-1;
r = n+1;
while (r-l > 1)
{
k = l+r>>1;
if (sumx[j1][j2][k]-sumx[j1][j2][i1-1] <= w) l = k; else r = k;
}
if (sumx[j1][j2][l]-sumx[j1][j2][i1-1] != w) l = -1;
return fx[j1][j2][i1][w]=l;
}
/*
求:以i1为上边界、i2为下边界、j1为左边界的矩形中,右边界为多少的矩形
重量和是w。如果不存在则返回-1
和上面对称
*/
int calcy(int i1, int i2, int j1, int w)
{
if (fy[i1][i2][j1][w] != 0) return fy[i1][i2][j1][w];
lfy.push_back(Quad(i1,i2,j1,w));
int l, r, k;
l = j1-1;
r = m+1;
while (r-l > 1)
{
k = l+r>>1;
if (sumy[i1][i2][k]-sumy[i1][i2][j1-1] <= w) l = k; else r = k;
}
if (sumy[i1][i2][l]-sumy[i1][i2][j1-1] != w) l = -1;
return fy[i1][i2][j1][w]=l;
}
/*
求(i1,j1)~(i2,j2)的矩形能否切出sta中的巧克力
*/
bool work(int i1, int i2, int j1, int j2, int sta)
{
//记忆化:求过了则直接返回
if (f[i1][i2][j1][sta] != 0) return f[i1][i2][j1][sta]==1;
if (bg[sta] == ed[sta]) return true;
//未求过,将该状态加入待清空队列
lf.push_back(Quad(i1,i2,j1,sta));
int i, sta2, x, y;
//枚举sta的每个非空真子集
for (i=bg[sta]; i<ed[sta]; ++i)
{
sta2 = c[i];
//尝试横向切
x = calcx(j1,j2,i1,suma[sta2]);
if (x != -1)
if (work(i1,x,j1,j2,sta2) && work(x+1,i2,j1,j2,sta-sta2))
{
f[i1][i2][j1][sta] = 1;
return true;
}
//尝试纵向切
y = calcy(i1,i2,j1,suma[sta2]);
if (y != -1)
if (work(i1,i2,j1,y,sta2) && work(i1,i2,y+1,j2,sta-sta2))
{
f[i1][i2][j1][sta] = 1;
return true;
}
}
f[i1][i2][j1][sta] = -1;
return false;
}
void dfs(int sta, int t)
{
if (t == MAXK)
{
if (sta > 0) c[nc++] = sta;
return;
}
if (sta&e[t]) dfs(sta-e[t], t+1);
dfs(sta, t+1);
}
int main()
{
freopen("chocolate.in", "r", stdin);
freopen("chocolate.out", "w", stdout);
e[0] = 1;
for (i=1; i<=MAXK; ++i) e[i] = e[i-1]*2;
//预处理每个sta有哪些非空真子集,连续存储在队列c中
nc = 1;
for (sta=1; sta<e[MAXK]; ++sta)
{
bg[sta] = nc; //bg表示sta的子集在c中的开头位置
dfs(sta, 0); //dfs求sta的非空真子集
--nc;
ed[sta] = nc; //ed表示sta的子集在c中的结尾位置
}
scanf("%d", &T);
while (T--)
{
scanf("%d%d%d", &n, &m, &K);
for (i=1; i<=n; ++i)
for (j=1; j<=m; ++j)
{
scanf("%d", &w);
//sumy[i][j][k]:从第i行到第j行,从第1列到第k列构成的矩形的重量和
sumy[i][i][j] = sumy[i][i][j-1]+w;
//sumx[i][j][k]:从第i列到第j列,从第1行到第k行构成的矩形的重量和
sumx[j][j][i] = sumx[j][j][i-1]+w;
}
for (l=1; l<n; ++l)
for (r=l+1; r<=n; ++r)
for (j=1; j<=m; ++j) sumy[l][r][j] = sumy[l][r-1][j]+sumy[r][r][j];
for (l=1; l<m; ++l)
for (r=l+1; r<=m; ++r)
for (i=1; i<=n; ++i) sumx[l][r][i] = sumx[l][r-1][i]+sumx[r][r][i];
for (i=1; i<=K; ++i) scanf("%d", &a[i]);
//求出{ai}的各个子集的重量和
//suma[sta]:sta中的巧克力的总重量
for (sta=0; sta<e[K]; ++sta)
{
suma[sta] = 0;
for (i=sta, j=1; i>0; i>>=1, ++j)
if (i&1) suma[sta] += a[j];
}
// 如果所有ai的总重量!=巧克力的总重量
if (suma[e[K]-1] != sumy[1][n][m])
{
printf("no\n");
continue;
}
//lf、lfx、lfy用于记录哪些状态被记忆化了,用于之后清零
lf.clear();
lfx.clear();
lfy.clear();
if (work(1,n,1,m,e[K]-1)) printf("yes\n");
else printf("no\n");
//清零记忆化过的状态
for (std::list<Quad>::iterator it=lf.begin(); it!=lf.end(); ++it) f[it->a][it->b][it->c][it->d] = 0;
for (std::list<Quad>::iterator it=lfx.begin(); it!=lfx.end(); ++it) fx[it->a][it->b][it->c][it->d] = 0;
for (std::list<Quad>::iterator it=lfy.begin(); it!=lfy.end(); ++it) fy[it->a][it->b][it->c][it->d] = 0;
}
return 0;
}