“科林明伦杯”哈尔滨理工大学第十届程序设计竞赛(同步赛)

差一点点就AK的一场。

差在高度相同的植物合并qwq

A. 点对最大值 (Nowcoder 5758 A)

题目大意

给定一棵树,有点权和边权,找到最大的点对价值。价值为两个点的点权和加上两点之间的边权和。

解题思路

考虑分治。

答案就是在根各子树中的最大值以及跨越根的最大值中取。

跨越根的最大值就是两个点,它们的点权加上到根节点的边权的和是第一大和第二大的。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=1e6+8;

int n,num;

int head[N];

int nxt[N*2],to[N*2],cost[N*2];

int val[N];

LL ans=0;

void add(int u,int v,int w){
    num++;
    nxt[num]=head[u];
    to[num]=v;
    cost[num]=w;
    head[u]=num;
    num++;
    nxt[num]=head[v];
    to[num]=u;
    cost[num]=w;
    head[v]=num;
}

LL DFS(int u,int fa){
    int cnt=0;
    LL qmq=val[u];
    LL qnq=-9147483647;
    LL tmp;
    for(int v,i=head[u];i;i=nxt[i]){
        v=to[i];
        if (v==fa) continue;
        ++cnt;
        tmp=DFS(v,u)+cost[i];
        ans=max(ans,tmp+val[u]);
        if (qmq<=tmp){
            qnq=qmq;
            qmq=tmp;
        }else if (qnq<tmp){
            qnq=tmp;
        }
    }
    ans=max(ans,qmq+qnq);
    return max(qmq,qnq);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        read(n);
        num=0;
        for(int a,b,i=1;i<n;++i){
            read(a);
            read(b);
            add(a,i+1,b);
        }
        for(int i=1;i<=n;++i){
            read(val[i]);
        }
        DFS(1,1);
        write(ans,'\n');
        ans=-9147483647;
        for(int i=0;i<=n;++i) head[i]=0;
    }
    return 0;
}


B. 减成一 (Nowcoder 5758 B)

题目大意

给定一个数组,每次选择一个区间使区间的数减一,问进行多少次可以让所有数变成\(1\)

解题思路

NOIP都考了两次了......

答案就是差分数组的元素与\(0\)\(max\)的和。

因为,假设相邻两个数\(a\),\(b\),如果\(a<b\)\(b\)的在减少成\(1\)的时候,\(a\)就早就减成\(1\)了,所以操作次数取大的那个。

\(a>b\),那么\(a\)在减到\(1\)那一刻,\(b\)早就变成\(1\)了,而在\(b\)变成\(1\)之后,\(b\)之后的那些数就不能减,只能等\(a\)减完再搞\(b\)后面的数,而\(b\)后面的数\(c\)减成1就还需要\(c-b\)次,因为前\(b\)次可以和之前减\(a\)时一起操作。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        LL ans=0;
        int n;
        cin>>n;
        int la=1;
        int qwq=0;
        while(n--){
            cin>>qwq;
            ans+=max(0,qwq-la);
            la=qwq;
        }
        cout<<ans<<endl;
    }
    return 0;
}


C. 面积 (Nowcoder 5758 C)

题目大意

给定正方形边长,算一个图形面积。它由一个正方形和四个以其边长为直径的半圆构成。

解题思路

算就可以了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const double pi=3.14;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        LL x;
        cin>>x;
        double ans=x*x+2*pi*(x*1.0/2)*(x*1.0/2);
        cout<<fixed<<setprecision(2)<<ans<<endl;
    }
    return 0;
}


D. 扔硬币 (Nowcoder 5758 D)

题目大意

同时扔\(n\)枚硬币,正反概率相同。已知至少有\(m\)枚硬币反面朝上,问恰有\(k\)枚硬币正面朝上的概率是多少。

解题思路

条件概率。

\(n-m<k\)答案就是0.
否则就是\(\dfrac{C^{k}_{n}}{\sum\limits_{i=0}^{n-m}C_{n}^{m+i}}\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 1e5+8;

const LL mo=1e9+7;

LL jie[N+1],invjie[N+1];

LL qpower(LL a,LL b){
    LL qwq=1;
    while(b){
        if (b&1) qwq=qwq*a%mo;
        b>>=1;
        a=a*a%mo;
    }
    return qwq;
}

LL inv(LL qwq){
    return qpower(qwq,mo-2);
}

LL C(int n,int m){
    if (n<m) return 0;
    else return jie[n]*invjie[m]%mo*invjie[n-m]%mo;
}

LL sum(int n,int l,int r){
    LL qwq=0;
    for(int i=l;i<=r;++i){
        qwq=(qwq+C(n,i))%mo;
    }
    return qwq;
}

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    jie[0]=jie[1]=invjie[0]=invjie[1]=1;
    for(int i=2;i<=N-3;++i){
        jie[i]=jie[i-1]*i%mo;
        invjie[i]=inv(jie[i]);
    }
    int t;
    cin>>t;
    while(t--){
        int n,m,k;
        cin>>n>>m>>k;
        LL ans=0;
        if (n-m>=k) ans=C(n,k)*inv(sum(n,m,n))%mo;
        cout<<ans<<endl;
    }
    return 0;
}


E. 赛马 (Nowcoder 5758 E)

题目大意

田忌赛马。已知对方马的出场顺序和战斗力,安排自己的马的出场顺序得到最大赢数。

解题思路

对方马战力从小到大依次匹配我方大于其战斗力的最小战斗力的马。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        int n;
        read(n);
        multiset<int> qwq;
        for(int u,i=1;i<=n;++i){
            read(u);
            qwq.insert(u);
        }
        vector<int> b(n);
        for(int i=1;i<=n;++i)
            read(b[i-1]);
        sort(b.begin(),b.end());
        int ans=0;
        for(auto i:b){
            auto it=qwq.upper_bound(i);
            if (it!=qwq.end()){
                ans++;
                qwq.erase(it);
            }
        }
        write(ans,'\n');
    }
    return 0;
}


F. 三角形 (Nowcoder 5758 F)

题目大意

将长为\(a\)的木棒分割成若干段长度为正整数的小木棒,要求任意三段都不能组成三角形,问最多分割成的小木棒的个数。

解题思路

斐波那契数列就是恰好不能组成三角形的边长极限。对其求和直到刚好大于等于\(a\)即可。

如果分割有剩余的话那这个就不能分割的。

数很大最好用int128。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        __int128 n;
        read(n);
        __int128 a,b,c;
        a=1;
        b=1;
        n=n-2;
        int cnt=1;
        while(n>=0){
            cnt++;
            c=a+b;
            n=n-c;
            a=b;
            b=c;
        }
        write(cnt,'\n');
    }
    return 0;
}


G. 养花 (Nowcoder 5758 G)

题目大意

小明有\(n\)棵植物,所有植物排成一排,植物的初始高度为数组\(h\),他想让植物的高度都恰好达到\(k\),小明有\(m\)瓶药水,但药水分为\(4\)种:

  • 选择一棵高度为\(a_0\)的植物变为\(b_0\)高度的植物
  • 选择一棵高度在\([a_1,a_2]\)区间内的植物变为\(b1\)高度的植物
  • 选择一棵高度为\(a_1\)的植物变为\([b_1,b_2]\)区间内某一高度的植物
  • 选择一棵高度在\([a_1,a_2]\)区间内的植物变为\([b_1,b_2]\)区间内某一高度的植物

由于第\(i\)瓶药水最多可以使用\(C_{i}\)次,小明想知道他最多让多少棵植物高度达到\(k\)

解题思路

决策类问题,很明显带有反悔的抉择,考虑网络流。

不同药水之间可以进行转移,即对于某个植物,我们用了某个药水后可以用另一个药水。于是这些药水可以构成一张图,对于一个植物,它可以从某点出发,根据边进行转移,而某些药水可以到达终点高度,于是我们只要能到达那些药水即可。

首先药水之间相互连边,能够抵达指定高度的药水再与终点连边,边流量都是无穷。

对于植物,连向能给它施加的药水,边权为无穷。

再将植物与起点连边,边权为\(1\),表示只能施加一次该植物。

我们发现还有药的使用次数,它是流进该药或流出该药的流量和,我们要对它进行限制,于是我们把药拆成两个点,流进点和流出点,两者的连边的流量即为药的使用次数,这样就限制了药的使用次数。

此时我们发现会\(T\),因为植物数太多了,但注意到这里我们只关心高度,而高度最多只有\(100\),那么我们可以把同高度的植物合并。

设高度为\(i\)的植物出现了\(cnt_i\)次,那么起点就连向代表植物高度为\(i\)的点,边容量就是\(cnt_i\)

然后跑一遍网络流,最大流即是答案。

综上,药拆点,限制药的流量。药流出点与能抵达的药流进点连边,以及和可抵达的终点连边,起点和植物高度连边,植物高度再和相应的药的流进点连边。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=4e6;

const int M=2e5;

const int INF=1e9+7;

int head[M],nxt[N*2],to[N*2],team[M],dis[M];

LL flow[N*2];

int n,e,st,en,num,m,k;

void add(int u, int v, int w) {
	num++;
	nxt[num] = head[u];
	to[num] = v;
	flow[num] = w;
	head[u] = num;
	num++;
	nxt[num] = head[v];
	to[num] = u;
	flow[num] = 0;
	head[v] = num;
}

bool BFS() {
	int l = 0, r = 1;
	team[1] = st;
	for(int i=0;i<=en;++i) dis[i]=0;
	dis[st] = 1;
	while (l < r) {
		int u = team[++l];
		for (int v, i = head[u]; i; i = nxt[i]) {
			v = to[i];
			if (dis[v] == 0 && flow[i]) {
				dis[v] = dis[u] + 1;
				team[++r] = v;
			}
		}
	}
	if (dis[en]) return true;
	else return false;
}

LL DFS(int u, LL f) {
	if (u == en) return f;
	LL qwq = 0, tmp = 0;
	for (int v, i = head[u]; i; i = nxt[i]) {
		v = to[i];
		if (dis[v] == dis[u] + 1 && flow[i]) {
			qwq = DFS(v, min(f - tmp, flow[i]));
			flow[i] -= qwq;
			flow[i ^ 1] += qwq;
			tmp += qwq;
			if (tmp == f) return tmp;
		}
	}
	return tmp;
}

struct yao{
    int z;
    int cnt;
    int a1,a2,b1,b2;
}yy[1006];

int cnt[105];

int main(void) {
    int kase; read(kase);
    num=1;
    for (int ii = 1; ii <= kase; ii++) {
        read(n);
        read(m);
        read(k);
        for(int h,i=1;i<=n;++i){
            read(h);
            cnt[h]++;
        }
        for(int i=1;i<=m;++i){
            read(yy[i].z);
            read(yy[i].cnt);
            if (yy[i].z==1){
                read(yy[i].a1);
                read(yy[i].b1);
            }else if (yy[i].z==2){
                read(yy[i].a1);
                read(yy[i].a2);
                read(yy[i].b1);
            }else if (yy[i].z==3){
                read(yy[i].a1);
                read(yy[i].b1);
                read(yy[i].b2);
            }else{
                read(yy[i].a1);
                read(yy[i].a2);
                read(yy[i].b1);
                read(yy[i].b2);
            }
        }
        st=0;
        en=2*m+1+100;
        for(int i=1;i<=100;++i){
            add(st,i+2*m,cnt[i]);
        }
        for(int i=1;i<=m;++i){
            add(2*i-1,2*i,yy[i].cnt);
        }
        for(int i=1;i<=m;++i){
            if (yy[i].z==1||yy[i].z==2){
                if (yy[i].b1==k) add(i*2,en,INF);
            }else{
                if (yy[i].b1<=k&&yy[i].b2>=k) add(i*2,en,INF);
            }
        }
        for(int i=1;i<=100;++i){
            for(int j=1;j<=m;++j){
                if (yy[j].z==1||yy[j].z==3){
                    if (yy[j].a1==i) add(2*m+i,2*j-1,INF);
                }else{
                    if (yy[j].a1<=i&&yy[j].a2>=i) add(2*m+i,2*j-1,INF);
                }
            }
        }
        for(int i=1;i<=m;++i){
            for(int j=1;j<=m;++j){
                if (i==j) continue;
                if (yy[i].z==1||yy[i].z==2){
                    if (yy[j].z==1||yy[j].z==3){
                        if (yy[i].b1==yy[j].a1) add(2*i,2*j-1,INF);
                    }else{
                        if (yy[i].b1>=yy[j].a1&&yy[i].b1<=yy[j].a2) add(2*i,2*j-1,INF);
                    }
                }else{
                    if (yy[j].z==1||yy[j].z==3){
                        if (yy[i].b1<=yy[j].a1&&yy[i].b2>=yy[j].a1) add(2*i,2*j-1,INF);
                    }else{
                        if (!(yy[i].b1>yy[j].a2||yy[i].b2<yy[j].a1)) add(2*i,2*j-1,INF);
                    }
                }
            }
        }
        int ans=0;
        while(BFS()){
            ans+=DFS(st,INF);
        }
        write(ans,'\n');
        for(int i=0;i<=en;++i) head[i]=0;
        for(int i=1;i<=100;++i) cnt[i]=0;
        num=1;
    }
    return 0;
}


H. 直线 (Nowcoder 5758 H)

题目大意

平面上的\(n\)条直线最多有多少个交点。

解题思路

学过高中数学的都知道答案是\(\dfrac{n(n-1)}{2}\)

\(int128\)即可。 或者python

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        __int128 n;
        read(n);
        __int128 ans = n*(n-1)/2;
        write(ans,'\n');
    }
    return 0;
}


I. 字典序 (Nowcoder 5758 I)

题目大意

给定一个\(n\)个数的数组\(A\),记\(S_i\)表示删去第\(i\)个数(从\(1\)开始)后的数组,现对这\(n\)个数组按照字典序从小到大排序,问最后这些数组的是多少。

解题思路

对这关于\(1-n\)的全排列快排即可,我们考虑比较函数。

设两个位置\(i,j\),我们考虑如何得知分别删去\(i,j\)后的两个数组的字典序大小。

\(i<j\)

那么对于\(cur<i\)位置的数,两个数组是相等的。

对于\(cur>j\)位置的数,两个数组也是相等的。

我们要比较的就是\(A[i+1...j]\)\(A[i...j-1]\)谁大谁小。

很显然我们可以依次比较,但这会超时。

注意到这里其实比较的都是当前位置与后一个位置的数的大小关系。

而我们要找的就是从第\(i\)位起第一个不是相等的位置,从这个位置就可以判断两者的大小。

预处理即可。

\(sign[i]\)表示第\(i\)位和第\(i+1\)位的大小关系,相等为\(0\),大于为\(1\),小于为\(-1\)

\(nxt[i]\)表示从第\(i\)位起第一个\(sign[i]\)不为\(0\)的位置。

如果\(i>j\)的返回值与其相反。

(其实做法和之前北大美团杯的交互题非常类似)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=1e5+8;

int n;

int a[N];

int ans[N];

int sign[N];

int nxt[N];

bool cmp(int x,int y){
    int mi=x<y?x:y;
    int ma=x+y-mi;
    int cur=nxt[mi];
    int si=x>y;
    if (cur>=ma) return (1^si);
    else if (sign[cur]==-1) return (0^si);
    else return (1^si);
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        read(n);
        for(int i=1;i<=n;++i){
            read(a[i]);
            ans[i]=i;
        }
        for(int i=1;i<n;++i){
            if (a[i]>a[i+1]) sign[i]=1;
            else if (a[i]==a[i+1]) sign[i]=0;
            else sign[i]=-1;
        }
        int cur=n+8;
        for(int i=n-1;i>0;--i){
            if (sign[i]!=0) cur=i;
            nxt[i]=cur;
        }
        sort(ans+1,ans+1+n,cmp);
        for(int i=1;i<=n;++i){
            printf("%d%c",ans[i],i==n?'\n':' ');
        }
    }
    return 0;
}


J. 最大值 (Nowcoder 5758 J)

题目大意

给定一个字符串\(a\),求该字符串长度最大的非前缀子串,使得它恰好为\(a\)的前缀。

解题思路

注意到结果对于长度具有单调性,二分长度\(l\),拿长度为\(l\)的前缀进行\(KMP\)匹配即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N = 1e5+8;

char s[N],ss[N];

int nxt[N],len;

char tmp[N];

void mk(){
    nxt[0]=0;
    int k=0;
    for(int i=1;i<len-1;++i){
        while(k>0&&ss[i]!=ss[k]) k=nxt[k-1];
        if (ss[k]==ss[i]) ++k;
        nxt[i]=k;
    }
}

bool check(int l){
    for(int i=0;i<l;++i){
        tmp[i]=s[i];
    }
    int q=0;
    for(int i=0;i<len-1;++i){
        while(q>0&&ss[i]!=tmp[q]) q=nxt[q-1];
        if (tmp[q]==ss[i]) ++q;
        if (q==l) return true;
    }
    return false;
}

int main(void) {
    int kase; read(kase);
    for (int ii = 1; ii <= kase; ii++) {
        scanf("%s",s);
        int l=0;
        int r=strlen(s);
        len=r;
        for(int j=0;j<len-1;++j) ss[j]=s[j+1];
        mk();
        while(l+1<r){
            int mid=(l+r)>>1;
            if (check(mid)) l=mid;
            else r=mid;
        }
        write(l,'\n');
    }
    return 0;
}


posted @ 2020-05-31 21:07  ~Lanly~  阅读(292)  评论(0编辑  收藏  举报