Task 2

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;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值