Luogu 1248 加工生产调度

题目

题目链接: https://www.luogu.org/problemnew/show/P1248

这道题目暴力显然不可取$ 1000!$ , DP 的状态不太容易定义,只能贪心大法

分析贪心

我们可以先假设只有两个物品$ A $ 和$ B $,则存在以下两种情况($ x $代表物品在 A 工厂加工所需时间,$ y $ 代表 B 工厂,以下同理)

  1. 先加工$ A $后加工$ B $,则时间为$ A.x + B.y + \max(A.y, B.x) $
  2. 先加工$ B $后加工$ A $,则时间为$ A.y + B.x + \max(A.x, B.y) $

则若先加工 A 比先加工 B 优,应满足
$$
A.x + B.y + \max(A.y, B.x) < A.y + B.x + \max(A.x, B.y) \\
\begin{align}
&= \max(A.y, B.x) – A.y – B.x < \max(A.x, B.y) – A.y – B.x \\
&= -\min(A.y, B.x) < -\min(A.x, B.y) \\
&= \min(A.x, B.y) < \min(A.y, B.x)
\end{align}
$$
然后放到cmp里,A 了?

等一下,这个方法……是错误的

为什么错误?

首先,我们需要知道在 C++ 标准库中,sort 要求排序运算符必须保证严格排序

什么是严格排序

若一个比较运算符,则应当满足

非自反性,非对称性,传递性,不可比性的传递性 这四点

好消息是,我们的函数并不具有不可比性的传递性

即我们的排序函数可能汇出形如下面的状况:

$ x < y $ 且 $y < z$ 但$ x \geq z$

这样一来,我们排出来的序列就不满足之前的要求了

排序的改进

所以我们的目的很简单,使我们的排序符号变成保证严格排序

我们可以把相等的情况单独提出来讨论

  1. $A.x < B.x$ 且 $A.y < B.y$ 按 $A.x < A.y$ 排序
  2. $A.x = B.x $ 且 $A.y = B.y $ 不做处理
  3. $ A.x > B.x $ 且 $A.y > B.y$ 按 $B.y < B.x$ 排序

多关键字排序即可

代码

#include <cstdio>
#include <algorithm>

inline int Max(int a, int b){return a > b? a: b;}

const int N = 1100;

int n;
struct node{
    int x, y, d, id;
}a[N];

bool cmp(node x, node y){
    if(x.d == y.d){
        if(x.d <= 0) 
            return x.x < y.x;
        else 
            return x.y > y.y;
    }
    else return x.d < y.d;
}

int main(){
#ifdef woshiluo
    freopen("luogu.1248.in", "r", stdin);
    freopen("luogu.1248.out", "w", stdout);
#endif
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){ scanf("%d", &a[i].x); }
    for(int i = 1; i <= n; i++){ 
        scanf("%d", &a[i].y); 
        a[i].id = i;
        a[i].d = (a[i].x == a[i].y ? 0: (a[i].x > a[i].y ? 1: - 1));
    }

    std::sort(a + 1, a + n + 1, cmp);

    int time_a = a[1].x, time_b = a[1].x + a[1].y;
    for(int i = 2; i <= n; i++){
        time_a += a[i].x;
        time_b = Max(time_a, time_b) + a[i].y;
    }
    printf("%d\n", time_b);
    for(int i = 1; i <= n; i++){ printf("%d ", a[i].id); }
}

后记

对与邻项排序的各种各样问题 ouuan 大佬的博客里有一篇讲的十分清楚 https://ouuan.github.io/%E6%B5%85%E8%B0%88%E9%82%BB%E9%A1%B9%E4%BA%A4%E6%8D%A2%E6%8E%92%E5%BA%8F%E7%9A%84%E5%BA%94%E7%94%A8%E4%BB%A5%E5%8F%8A%E9%9C%80%E8%A6%81%E6%B3%A8%E6%84%8F%E7%9A%84%E9%97%AE%E9%A2%98/

博主也是根据这篇博文学的,%%% ouuan

Educational Codeforces Round 60 (Rated for Div. 2) 解题报告

A. Best Subsegment

给定一串数列, 求区间最大平均值

显然,最大的区间平均值可以就是数列中最大的那个,然后基于最大的点想两边枚举区间长度

#include <cstdio>
#include <algorithm>
inline int Max(int a, int b){return a > b? a: b;}
const int N = 1e5 + 1e4;
int n, max, max_id, len, rig, max_len;
int a[N];
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        if(a[i] > max)
            max = a[i];
    }
    for(int i = 1; i <= n; i++){
        if(a[i] == max && ( a[i - 1] != max || i == 1 ) ){
            rig = i; len = 0;
            while( a[rig] == a[i] && rig <= n ){
                rig++; len++;
            }
            max_len = Max(len, max_len);
        }
    }
    printf("%d", max_len);
}

B. Emotes

总共 $n$ 个表情, 你需要输出一个长度为 $m$ 的长度序列,但是同一个表情不能连续出现 $k$ 次

排个序,每个周期按,最大的出现 $k$ 次,然后出现一次第二大的,枚举周期即可

#include <cstdio>
#include <algorithm>
const int N = 2e5 + 1e4;
int n, m, k, tmp;
long long ans;
int a[N];
bool cmp(int a, int b){
    return a > b;
}
int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
    }
    std::sort(a + 1, a + n + 1, cmp);
    if( m <= k ){
        printf("%lld", 1LL * a[1] * m);
        return 0;
    }
    else {
        ans = 1LL * a[1] * k + (long long) a[2];
        ans *= (m / (k + 1) );
        m %= (k + 1);
        ans += 1LL * a[1] * m;
        printf("%lld", ans);
    }
}

C. Magic Ship

本来是到直接两点坐标差的绝对值之和就可以的

然后有洋流

我们设每一轮洋流为一个周期,然后二分最少需要多少周期

然后 $O(n)$ 判断不在整数周期内部的就可以

#include <cstdio>
inline long long Aabs(long long a){return a < 0? (0 - a): a;}
const long long N = 1e5 + 1e4;
long long x1, y1, x2, y2, dx, dy, n, tmp, ans, mid;
char str[N];
int main(){
    scanf("%lld%lld", &x1, &y1);
    scanf("%lld%lld", &x2, &y2);
    scanf("%lld", &n);
    scanf("%s", str + 1);
    for(long long i = 1; i <= n; i++){
        if(str[i] == 'U') dy++;
        if(str[i] == 'D') dy--;
        if(str[i] == 'L') dx--;
        if(str[i] == 'R') dx++;
    }
    long long left = 0, rig = (int)2e9;
    while(left <= rig){
        mid = (left + rig) >> 1;
        if( Aabs(x1 + dx * mid - x2) + Aabs(y1 + dy * mid - y2) <= n * mid ) rig = mid - 1;
        else left = mid + 1;
    }
    x1 += dx * rig;
    y1 += dy * rig;
    ans = n * rig;
    if( Aabs(x1 - x2) + Aabs(y1 - y2) <= ans){
        printf("%lld", ans);
        return 0;
    }
    for(long long i = 1; i <= n; i++){
        if(str[i] == 'U') y1++;
        if(str[i] == 'D') y1--;
        if(str[i] == 'L') x1--;
        if(str[i] == 'R') x1++;
        ans++;
        if( Aabs(x1 - x2) + Aabs(y1 - y2) <= ans ){
            printf("%lld", ans);
            return 0;
        }
    }
    printf("-1");
}


]]>

Codeforces Round #538 (Div. 2) 解题报告

A Got Any Grapes?

顺序判断即

比赛的时候忘记写 else 既然 PP 了,然后就 FST

#include <cstdio>
int an,dm,mi;
int gr,pu,bl;
int main(){
    scanf("%d%d%d", &an, &dm, &mi);
    scanf("%d%d%d", &gr, &pu, &bl);
    if(an > gr) {
        printf("NO\n");
        return 0;
    }
    else gr -= an;
    if(pu + gr < dm){
        printf("NO\n");
        return 0;
    }
    else {
        if(pu <= dm) {dm -= pu; pu = 0;}
        else {pu -= dm; dm = 0;}
        if(gr < dm) {
            printf("NO\n");
            return 0;
        }
        else gr -= dm;
    }
    if(gr + pu + bl < mi) printf("NO\n");
    else printf("YES\n");
}

B Yet Another Array Partitioning Task

反正最后还是要前$ m \times k $ 个数字

直接离散化,然后见当前区间有$ m $ 个就收

#include <cstdio>
#include <algorithm>
const long long N = 2e5 + 1e4;
struct node{
    long long now, id;
}b[N];
long long n, m, k, cnt, time;
long long a[N];
bool vis[N];
bool cmp(node a, node b){
    return a.now > b.now;
}
int main(){
    scanf("%lld%lld%lld", &n, &m, &k);
    for(long long i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        b[i].now = a[i], b[i].id = i;
    }
    std::sort(b + 1, b + n + 1, cmp);
    long long tmp = m * k;
    for(long long i = 1; i <= tmp; i++) vis[ b[i].id ] = true, cnt += b[i].now;
    printf("%lld\n",cnt);
    cnt = 0;
    for(long long i = 1; i <= n; i++){
        cnt += vis[i];
        if(cnt == m){
            time++;
            time < k && printf("%lld ",i);
            cnt = 0;
        }
    }
}

C. Trailing Loves (or L’oeufs?)

求 $ b $ 进制下 $ n! $ 的末尾的 0 的个数

显然我们要求中间乘出来有多少个$b$

接下来就是分解质因数,然后枚举求最小值即可

#include <cstdio>
#include <cmath>
const long long INF = 1e18 + 1e17;
inline long long Min(long long a, long long b){return a < b? a: b;}
struct node{
    long long now, cnt;
}pri[(int)(1e6)];
int pcnt;
long long n, b, tmp, cnt, ans = INF;
inline void get_pri(long long b){
    tmp = std::sqrt(b);
    for(long long i = 2; i <= tmp; i++){
        if(b % i == 0){
            pri[ ++pcnt ] = (node){i, 0};
            while(b % i == 0){
                pri[pcnt].cnt++;
                b/=i;
            }
        }
    }
    if(b != 1) pri[ ++pcnt ] = (node){b, 1};
}
int main(){
    scanf("%lld%lld", &n, &b);
    get_pri(b);
    for(int i = 1; i <= pcnt; i++){
        tmp = 1; cnt = 0;
        while(tmp * pri[i].now <= n){
            tmp *= pri[i].now;
            if(tmp < 0 || tmp % pri[i].now != 0) break;
            cnt += n / tmp;
        }
        ans = Min(ans, cnt/pri[i].cnt);
    }
    printf("%lld\n", ans);
}

D. Flood Fil

有一个非常显然的地方,就是我们每次有两个状态,当前联通部分向左对其或者想右对其

然后我们先把数列中的联通部分预处理出来,然后区间 dp 即可

#include <cstdio>
#include <cstring>
inline int Min(int a, int b){return a < b? a: b;}
const int N = 5100;
int n;
int a[N], l[N], r[N], f[N][N];
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    l[1] = 1;
    for(int i = 2; i <= n; i++){
        if(a[i] == a[i - 1]) l[i] = l[i - 1];
        else l[i] = i;
    }
    r[n] = n;
    for(int i = n - 1; i >= 1; i--){
        if(a[i] == a[i + 1]) r[i] = r[i + 1];
        else r[i] = i;
    }
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i<= n;i ++) f[ l[i] ][ r[i] ] = 0;
    for(int len = 0; len < n; len++){
        for(int i = 1,j; i + len <= n; i++){
            j = i + len;
            if(i > 1 && j < n && a[i - 1] == a[j + 1])
                f[ l[i - 1] ][ r[j + 1] ] = Min(f[ l[i - 1] ][ r[j + 1] ],f[i][j] + 1);
            if(i > 1)
                f[ l[i - 1] ][j] = Min(f[ l[i - 1] ][j], f[i][j] + 1);
            if(j < n)
                f[i][ r[j + 1] ] = Min(f[i][ r[j + 1] ], f[i][j] + 1);
        }
    }
    printf("%d", f[1][n]);
}

E. Arithmetic Progression

交互题目,60次询问内知道当前乱序等差数列的首项和公差

先二分找最大的,然后random_shuffle随机化询问,求与最大项差的 gcd 即为公差

#include <cstdio>
#include <algorithm>
int gcd(int a, int b){return b? gcd(b ,a%b): a;}
inline int Min(int a, int b){return a < b? a: b;}
inline int Aabs(int a){return a < 0? (0 - a): a;}
const int N = 1e6 + 1e5;
int n, global_tmp, d, max, chance_cnt = 60;
int a[N];
inline bool has_num(int now){
    printf("> %d\n",now);
    fflush(stdout);
    scanf("%d", &global_tmp);
    chance_cnt--;
    return global_tmp;
}
inline int binary_search_max(int max_limit){
    int left = 0, rig = max_limit, mid, res;
    while(left <= rig){
        mid = (left + rig) >> 1;
        if( has_num(mid) ) left = mid + 1;
        else rig = mid - 1, res = mid;
    }
    return res;
}
int main(){
    scanf("%d", &n);
    max = binary_search_max(1e9);
    for(int i = 1; i <= n; i++) a[i] = i;
    for(int i = 1; i <= 5; i++) std::random_shuffle(a + 1, a + n + 1);
    for(int i = 1; i <= Min(n ,chance_cnt); i++){
        printf("? %d\n", a[i]);
        fflush(stdout);
        scanf("%d", &global_tmp);
        if(global_tmp == max) continue;
        if(d == 0) d = Aabs(max-global_tmp);
        d = gcd(d , Aabs(max-global_tmp));
    }
    fflush(stdout);
    printf("! %d %d\n", max - (n - 1) * d, d);
}

F. Please, another Queries on Array?

这个题目首先得知道$\varphi(n)$的求法

然后就是乘积线段树和或线段树维护一下

就没有然后了

#include <cstdio>
const int N = 4e5 + 1e4;
const long long mod = 1e9 + 7;
int n, q, x, y, z, pcnt;
int a[N];
long long p[310], pri[N], inv[310];
char op[10];
inline long long ksm(long long a, long long p){
    long long res = 1;
    while(p){
        if(p & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        p >>= 1;
    }
    return res;
}
inline void prime(){
    for(int i = 2; i <= 300; i++){
        for(int j = 1; j <= pcnt; j++)
            if(i % p[j] == 0) pri[i] |= pri[ p[j] ];
        if(pri[i] == 0) {p[++pcnt] = i; pri[i] = (1LL << (pcnt - 1LL));}
    }
}
inline void get_inv(){
    for(int i = 1; i <= pcnt; i++)
        inv[i] = ksm(p[i], mod - 2);
}
// Segment Tree Start
struct node{
    long long val,pri;
    void operator+= (const node &b){
        this -> val = (this -> val * b.val) % mod;
        pri |= b.pri;
    }
}tree[N << 2], lazy[N << 2];
inline void pushup(int now){
    tree[now].val = (tree[now << 1].val * tree[now << 1 | 1].val) % mod;
    tree[now].pri = tree[now << 1].pri | tree[now << 1 | 1].pri;
}
inline void pushdown(int now, int lson, int rson){
    if(lazy[now].pri != 0){
        tree[now << 1].val  =  (1LL * tree[now << 1].val * ksm(lazy[now].val, lson)) % mod;
        tree[now << 1 | 1].val  =  (1LL * tree[now << 1 | 1].val * ksm(lazy[now].val, rson)) % mod;
        tree[now << 1].pri |= lazy[now].pri;
        tree[now << 1 | 1].pri |= lazy[now].pri;
        lazy[now << 1].val = (1LL * lazy[now << 1].val * lazy[now].val) % mod;
        lazy[now << 1 | 1].val = (1LL * lazy[now << 1 | 1].val * lazy[now].val) % mod;
        lazy[now << 1].pri |= lazy[now].pri;
        lazy[now << 1 | 1].pri |= lazy[now].pri;
        lazy[now].val = 1LL; lazy[now].pri = 0;
    }
}
inline void query_mut(int now, int left, int rig, int from, int to, int val){
    if(from <= left && rig <= to){
        tree[now].val = (1LL * tree[now].val * ksm(val, (rig - left + 1LL))) % mod;
        tree[now].pri |= pri[val];
        lazy[now].val = (lazy[now].val * val) % mod;
        lazy[now].pri |= pri[val];
        return ;
    }
    int mid = (left + rig) >> 1;
    pushdown(now, mid - left + 1LL, rig - mid);
    if(from <= mid) query_mut(now << 1, left, mid, from, to, val);
    if(to > mid) query_mut(now << 1 | 1, mid + 1, rig, from, to, val);
    pushup(now);
}
inline node query_sum(int now, int left, int rig, int from, int to){
    if(from <= left && rig <= to) return tree[now];
    int mid = (left + rig) >> 1;
    node res = (node){1, 0};
    pushdown(now, mid - left + 1LL, rig - mid);
    if(from <= mid) res += query_sum(now << 1, left, mid, from, to);
    if(to > mid) res += query_sum(now << 1 | 1, mid + 1, rig, from, to);
    pushup(now);
    return res;
}
inline void build_tree(int now, int left, int rig){
    lazy[now].val = 1; lazy[now].pri = 0;
    if(left == rig){
        scanf("%lld", &tree[now].val);
        tree[now].pri = pri[ tree[now].val ];
        return ;
    }
    int mid = (left + rig) >> 1;
    build_tree(now << 1, left, mid);
    build_tree(now << 1 | 1, mid + 1, rig);
    pushup(now);
}
// Segment Tree End
int main(){
    prime();
    get_inv();
    scanf("%d%d", &n, &q);
    build_tree(1, 1, n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    while(q--){
        scanf("%s", op);
        if(op[0] == 'M'){
            scanf("%d%d%d", &x, &y, &z);
            query_mut(1, 1, n, x, y, z);
        }
        else {
            scanf("%d%d", &x, &y);
            node tmp = query_sum(1, 1, n, x, y);
            for(int i = 1; i <= pcnt; i++){
                if((tmp.pri >> (i - 1LL)) & 1LL)
                    tmp.val = (((tmp.val * (p[i] - 1) ) % mod) * inv[i]) % mod;
            }
            printf("%lld\n", tmp.val);
        }
    }
}


]]>

Codeforces Round #530 (Div. 2) 解题报告

A

题目大意: 有一个高h重w的雪球从山上滚下来,每下降一米,重量+=高度,中间会碰到两个石头,碰到后雪球减去石头的重量,问到达高度为 0 是重量为多少

小于0则看做0

模拟题……

#include <cstdio>
int w,h;
int u1,u2,d1,d2;
int main(){
    scanf("%d%d%d%d%d%d",&w,&h,&u1,&d1,&u2,&d2);
    while(h){
        w+=h;
        if(h==d1) w-=u1;
        if(h==d2) w-=u2;
        if(w<0) w=0;
        h--;
    }
    printf("%d",w);
}

B

现在需要画$n$个正方形,问最少需要几个单位线段可以保证画出来的$n$个正方形的每条边都可以通过已有线段平行得到

贪心就对了

#include <cstdio>
#include <cmath>
int n,tmp,ans;
int main(){
    scanf("%d",&n);
    tmp=std::sqrt(n);
    if(n%tmp==0){
        printf("%d",tmp+(n/tmp));
        return 0;
    }
    else {
        ans=tmp+(n/tmp);
        printf("%d",ans+1);
    }
}

C

给你一个字符串,如果一个字母后跟的是*表示这个字母可以被删除,保留或重复多次,如果跟的是?表示这个字母可以被删除或者保留,如果都不是,则表明其必须留在原位置

贪心题目,比较原字符串长度与要求的长度,然后分段讨论

#include <cstdio>
#include <cstring>
int k,len,zlen,sfcnt,tmp;
bool sf,cd;
char s[1000];
int main(){
    scanf("%s",s+1);
    scanf("%d",&k);
    len=strlen(s+1);
    for(int i=1;i<=len;i++){
        if(s[i]>='a'&&s[i]<='z')
            zlen++;
        if(s[i]=='?') sfcnt++;
        if(s[i]=='*') {cd=true;sfcnt++;}
    }
    if(zlen>k){
        if(sfcnt<zlen-k){
            printf("Impossible");
            return 0;
        }
        for(int i=1;i<=len;i++){
            if(s[i]=='*'||s[i]=='?') continue;
            if(s[i+1]=='?'||s[i+1]=='*'){
                if(zlen>k) {zlen--;sfcnt--;}
                else printf("%c",s[i]);
            }
            else printf("%c",s[i]);
        }
    }
    else if(zlen==k){
        for(int i=1;i<=len;i++){
            if(s[i]=='*'||s[i]=='?') continue;
            else printf("%c",s[i]);
        }
    }
    else if(zlen<k){
        if(!cd){
            printf("Impossible");
            return 0;
        }
        for(int i=1;i<=len;i++){
            if(s[i]=='*'||s[i]=='?') continue;
            else if(s[i+1]=='*'){
                printf("%c",s[i]);
                while(1){
                   if(zlen>=k) break;
                   printf("%c",s[i]);
                   zlen++;
                }
            }
            else printf("%c",s[i]);
        }
    }
}

D

现在已知树的形态与奇数深度点到根的长度(最短路径所经过的边的边权和)

因为交叉未知……所以直接贪心!!!

#include <cstdio>
#include <cstdlib>
inline int Min(int a,int b){return a<b?a:b;}
const int N=1e5+2e4;
const int INF=2147483640;
int n,v;
long long sum=0;
int s[N],a[N],son[N];
// edge start
struct edge{
    int to,next;
}e[N<<1];
int ehead[N],ecnt;
inline void add_edge(int now,int to){
    ecnt++;
    e[ecnt].to=to;
    e[ecnt].next=ehead[now];
    ehead[now]=ecnt;
}
// edge end
void dfs(int now,int val){
    if(s[now]<=-1){
        int tmp=INF;
        for(int i=ehead[now];i;i=e[i].next){
            tmp=Min(s[e[i].to],tmp);
        }
        if(tmp==INF) return ;
        tmp-=val;a[now]=tmp;
        for(int i=ehead[now];i;i=e[i].next){
            s[e[i].to]-=val+a[now];
        }
        for(int i=ehead[now];i;i=e[i].next){
            dfs(e[i].to,val+a[now]);
        }
        return ;
    }
    a[now]=s[now];
    for(int i=ehead[now];i;i=e[i].next){
        dfs(e[i].to,val+a[now]);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=2;i<=n;i++){
        scanf("%d",&v);
        add_edge(v,i);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&s[i]);
        if(s[i]!=-1&&s[i]<s[1]){
            printf("-1");
            return 0;
        }
    }
    dfs(1,0);
    sum=0;
    for(int i=1;i<=n;i++){
        if(a[i]<0){
            printf("-1");
            return 0;
        }
        sum+=a[i];
    }
    printf("%lld",sum);
}
]]>