T1
题目大意:
两摞纸牌,一一对应,第二副最多能有多少张被第一副完全覆盖(不可旋转)
简单分析:
对x[i]从小到大进行排序。
每次遇到B类牌,将y值插入进某个数据结构中
遇到A类牌(不考虑x),找这个数据结构中y值尽可能大且不超过这张牌的y值。
标算:
数据结构选择:1.插入一个数 2.删除不超过某个数的最大的数
数组实现数据结构,复杂度n^2
手写? 平衡树? 权值线段树?(我不会啊)
STL大法 ——–> multiset
#include<cstdio>
#include<algorithm>
#include<set>
#include<cctype>
using namespace std;
const int N = 100000 + 10;
int n;
multiset<int> s;
struct hh {
int x, y;
}a[N], b[N];
inline bool cmp(hh a, hh b) {
return a.x < b.x;
}
inline int rd() { //快读
int ans = 0; char c;
while(!isdigit(c = getchar()));
do ans = ans*10 + c - '0';
while(isdigit(c = getchar()));
return ans;
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) a[i].x = rd(), a[i].y = rd();
for(int i = 1; i <= n; ++i) b[i].x = rd(), b[i].y = rd();
sort(a+1, a+n+1, cmp); sort(b+1, b+n+1, cmp);
s.clear();
int k = 1, ans = 0;
for(int i = 1; i <= n; ++i) {
while(a[i].x >= b[k].x && k <= n) {
s.insert(b[k].y); k++;
}
if(s.empty()) continue;
multiset<int>::iterator it = s.upper_bound(a[i].y);
if(it == s.begin()) continue;
it--; ans++;
s.erase(it);
}
printf("%d\n", ans);
return 0;
}
T2
题目大意:
组成1~n之间的任意数所需要的最少硬币数以及在这数量上的方案数
简单分析:
最少个数很简单, 取个对数就好了
而看到方案数, 就应联想到DP解决, 同时发现一些规律, 按照不重不漏的原则, 每次取的硬币面值应该递增
标算:
设计状态:dp[i][j][k]表示当前选了i个金币, 总价值和为j, 最大的一枚面值为k
转移:在取一枚比k大的金币就好
复杂度:状态复杂度n^3, 转移复杂度1, 总复杂度n^3
优化:滚动数组
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const double eps = 0.000000001;
const int N = 1000 + 10;
int n, sum, ans;
int dp[N][N], DP[N][N]; //滚动数组优化空间
//dp[i][j] 当前金币和为j, 最大的一枚(最后取的)面值为j
int main() {
scanf("%d", &n);
sum = (int)(log(n)/log(2) + eps) + 1; //最少金币数
dp[1][1] = 1;
for(int i = 1; i < sum; ++i) { //枚举每个金币
for(int j = 1; j <= n; ++j) { //每个金币
for(int k = 1; k <= n; ++k) { //枚举当前已选金币中的最大的一枚金币
if(dp[j][k]) {
for(int l = k+1; l <= j+1; ++l) { //枚举下一枚
//特点:下一枚一定比当前这一枚大 且 比总金币数+1小
DP[min(n, j+l)][l] += dp[j][k];
}
}
}
}
for(int j = 1; j <= n; ++j) { //滚动
for(int k = 1; k <= n; ++k) {
dp[j][k] = DP[j][k];
DP[j][k] = 0;
}
}
}
for(int j = 1; j <= n; ++j) ans += dp[n][j]; //统计答案
printf("%d %d\n", sum, ans);
return 0;
}
T3
不可做的题目大意:
一开始有n个数,一段区间的价值为这段区间相同的数的对数。我们想把这n个数切成恰好k段区间。之后这n个数的价值为这k段区间的价值和。我们想让最终这n个数的价值和尽可能少。
用命分析:
名叫dp, 内容应该也是dp吧~~~
咋都看不懂的标算:
dp[i][j] 1~i 切了j刀,的最优解
#include<cstdio>
#include<cstring>
typedef long long ll;
const int N = 1e6 + 10;
const ll inf = (1LL << 60);
int c[N], a[N];
ll f[N], g[N];
int p, q, n, k;
ll tot;
void move(int l, int r) { // [p, q]之前的区间
while(l < p) p--, tot += c[a[p]], c[a[p]]++;
while(q < r) q++, tot += c[a[q]], c[a[q]]++;
while(p < l) c[a[p]]--, tot -= c[a[p]], p++;
while(r < q) c[a[q]]--, tot -= c[a[q]], q--;
}
void work(int l, int r, int fl, int fr) {
//需要求dp[fl] ~ dp[fr]
//最优解一定从 l~r 中的某一个转移过来
if(fl > fr) return;
int mid = (fl+fr)>>1;
ll mx = inf, mi = 0;
for(int i = l; i <= r; ++i) {
if(i < mid) {
move(i+1, mid);
if(f[i] + tot < mx) {
mx = f[i] + tot;
mi = i;
}
}
}
g[mid] = mx;
work(l, mi, fl, mid-1);
work(mi, r, mid+1, fr);
}
int main() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
f[0] = 0;
for(int i = 1; i <= n; ++i) f[i] = inf;
for(int i = 1; i <= k; ++i) {
p = 1, q = 0, tot = 0;
memset(c, 0, sizeof(c));
work(0, n-1, 1, n);
for(int i = 0; i <= n; ++i) {
f[i] = g[i];
g[i] = 0;
}
}
printf("%d\n", f[n]);
return 0;
}