“科林明伦杯”哈尔滨理工大学第十届程序设计竞赛(同步赛)
差一点点就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;
}