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

题目链接

按难度顺序

C.面积

题意:


4如图所示,正方形周围接4个半圆,给正方形边长,求图形的面积
题解:
正方形边长的一半就是圆的半径
42pir24个半圆就是两个圆,面积是2*pi*r^2
dd正方形d*d加一起
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
//const int mod=1e9+7;
const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};


int main()
{
    //ios::sync_with_stdio(false);
    //cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    while(_--){
        double x;cin>>x;
        double ans=(2*(x/2)*(x/2)*3.14)+x*x;
        printf("%.2f\n",ans);
    }
    
    return 0;
}


E.赛马

题意:
n小明和同学各有n匹马,每个马有战力值
两匹马比赛战力高的获胜
已知同学的出战顺序,小明最多能获胜几局,每匹马只能用一次
题解:
贪心
对自己和同学的马的战力都进行排序
每次让自己的马里战力尽可能低的去挑战同学最低的并能获胜
这样找下来就能找到最大次数
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
//const int mod=1e9+7;
const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};

int a[maxn],b[maxn];

int main()
{
    //ios::sync_with_stdio(false);
    //cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    while(_--){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        for(int i=1;i<=n;i++)cin>>b[i];
        sort(a+1,a+1+n);sort(b+1,b+1+n);
        int ans=0,x=1;
        for(int i=1;i<=n;i++){
			while(a[x]<=b[i]&&x<=n)x++;
			if(x==n+1) break;
			x++;ans++;	
		}
        cout<<ans<<endl;
    }

    return 0;
}


H.直线

题意:
n线平面内有n条直线,问最多有多少个交点
题解:
n线n(n1)/2n条直线两两相交,一共n*(n-1)/2个交点
n<=1e15,longlongint128n<=1e15,会爆longlong,所以考虑高精度或_int128
python我这里用的是python
AC代码

t=int(input())
for i in range(t):
    n=int(input())
    print(n*(n-1)//2)

B. 减成一

题意:
nn个数,每次可以对区间进行减一
1最少多少次能让每个数都变成1
题解:
差分
01假设第0个数是1
1然后求出差分数组,想让每个数都变成1
其实就是求这些差分数组正数的和
因为只有后一个数比前一个大的时候,需要把后一个数再多减去
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};

int a[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    while(_--){
        int n;cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        a[0]=1;
        int ans=0;
        for(int i=1;i<=n;i++)
            if(a[i]-a[i-1]>0)ans+=a[i]-a[i-1];
        cout<<ans<<endl;
    }
    return 0;
}


J.最大值

题意:
给一个字符串,问字符串中
有哪个非前缀字符串等于字符串的前缀
问这个非前缀字符串最长是多少
题解:
这道题我看方法有很多
KMP我写的方法是魔改了一下KMP算法
将原字符串设为模式串
把原字符串的第一个字符去掉后去作为整串
KMPKMP然后进行KMP匹配,进行KMP匹配的过程中
维护一下模式串的指针最远能指到哪个位置,这个位置就是最长匹配
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
//const int mod=1e9+7;
const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};

string pa,str;
int nx[maxn],ans;
inline void GetNext()
{
    int i = 0, j = -1, len = pa.length();
    nx[i] = j;
    while(i < len){
        while( j != -1 && pa[i] != pa[j]) j = nx[j];
        nx[++i] = ++j;
    }
}
int Kmp()
{
    GetNext();
    int i =0, j = 0, lens=str.length(),lenp=pa.length();
    while(i < lens && j < lenp){
        while( j != -1 && str[i] != pa[j]) j = nx[j];
        i++, j++;
        ans=max(ans,j);
    }
    if(j == lenp) return i - lenp;///返回模式串在主串中首次出现的下标 +1是位置
    else return -1;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    while(_--){
        cin>>pa;ans=0;
        str=pa;str[0]='.';
        Kmp();cout<<ans<<endl;
    }

    return 0;
}

D.扔硬币

题意:
n扔n个硬币,正反概率各为一半
mk如果已知最少有m个硬币是反面,恰好有k个硬币是正面的概率是多少
题解:
nmk首先特判一下,如果n-m即剩余的硬币根本不够k个
k那么说明根本不可能有k个硬币是正面
0概率一定为0

然后可以看出来,这是一个条件概率
条件概率的公式为
P(AB)=P(AB)/P(B)P(A|B)=P(AB)/P(B)
P(B)m很明显,这里的P(B)为至少m个硬币是反面
01m1那么求恰好有0个,1个,…,m-1个硬币是反面
1P(B)Cni/(2n)用1减去即是P(B),对于每种情况就是C_n^i/(2^n)
由于是模数所以除法都需要用逆元
P(AB)P(B)P(AB)其实由于特判和P(B)没有什么关系
k结果就是恰好k个硬币是正面
Cnk/(2n)直接组合即可,C_n^k/(2^n)
P(AB)inv(P(B))然后用P(AB)*inv(P(B))
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};


ll fact[maxn],inv1[maxn],a[maxn];
ll Pow(ll a, ll b){
	ll ans = 1;
	while(b > 0){
		if(b & 1){
			ans = ans * a % mod;
		}
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}
//逆元
ll inv(ll b){
    return Pow(b,mod-2)%mod;
}



ll C(ll n,ll m){
    if(m>n||n<0||m<0)return 0;
    if(m==0||m==n) return 1;
    ll res=(fact[n]*inv1[m]%mod*inv1[n-m])%mod;
    return res;
}

void init() {
	fact[0] = 1;
	for (int i = 1; i < maxn; i++) {
		fact[i] = fact[i - 1] * i %mod;
	}
	inv1[maxn - 1] = Pow(fact[maxn - 1], mod - 2);
	for (int i = maxn - 2; i >= 0; i--) {
		inv1[i] = inv1[i + 1] * (i + 1) %mod;
	}
}




int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    a[0]=1;
    init();
    for(int i=1;i<=100000;i++)a[i]=a[i-1]*inv(2)%mod;
    while(_--){
        ll n,m,k;cin>>n>>m>>k;
        if(n-m<k)cout<<0<<endl;
        else{
            ll y=0;
            for(int i=0;i<m;i++)
                y=(y+C(n,i)*a[n])%mod;
            y=(mod+1-y)%mod;
            ll x=C(n,k)*a[n]%mod;
            cout<<x*inv(y)%mod<<endl;
        }
    }

    return 0;
}


A.点对最大值

题意:
一棵树上的点和边都有对应的权值
对于树上点对的权值为,路径的边权加上始末的点权
求点对的最大值
题解:
DP树形DP
dpi对于每个dp数组维护以i为端点的最大点对值
0可以将每个点看作与自己相连,边权为0
然后始末点都是自己,那么点对值就是点权的二倍
对于每一条边,更新如果这条边连接他两端对应的链
是否能得到一个更大的结果,每次更新这个最大结果
dpdp数组更新从任一子节点到它的最大点对值
dp[v]dp[u]每次从dp[v]转移到dp[u]的时候
dp[v]val[v]+w+val[u]使vudp[v]-val[v]+w+val[u]使得v为端点的链端点变u
然后最后输出维护的最大结果
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};

vector<pii> g[maxn];
ll val[maxn],dp[maxn],ans;
void dfs(int u,int fa){
    dp[u]=2*val[u];
    for(auto i:g[u]){
        int v=i.fi,w=i.se;
        if(v==fa)continue;
        dfs(v,u);
        ans=max(ans,dp[u]-val[u]+dp[v]-val[v]+w);
        dp[u]=max(dp[u],val[u]+dp[v]-val[v]+w);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    while(_--){
        int n;cin>>n;
        for(int i=1;i<=n;i++)g[i].clear();
        for(int i=2;i<=n;i++){
            int v,w;
            cin>>v>>w;
            g[i].pb(mp(v,w));
            g[v].pb(mp(i,w));
        }
        for(int i=1;i<=n;i++)cin>>val[i];
        ans=-8e18;
        dfs(1,0);
        cout<<ans<<endl;
    }
    return 0;
}


G.养花

题意:
nhik小明有n棵植物,每个高度为h_i,他想让他们变为k
他可以做以下四种操作,每组给定对应次数
1.a0b01.将高度为a_0的变为b_0
2.[a1,a2]b12.[a_1,a_2]区间的某个高度变为b_1
3.a1[b1,b2]3.a_1的变为[b_1,b_2]区间的某个高度
4.[a1,a2][b1,b2]4.[a_1,a_2]区间的某个高度变为[b_1,b_2]的某个高度
k问最多能让多少植物变成k高度
题解:
网络流
这道题存在高度的转换和最终个数的统计
k那么可以用一个超级源点,并让k为汇点
1k广k超级源点指向每个高度流量为1,到达k即为一条增广路,说明可以变为k
1a0b0ci操作1,转换到网络流就是让a_0指向b_0,流量为次数c_i
2b1ci,a1a2操作2,即将b_1裂点,流量为c_i表示次数,a_1到a_2流向它
32a1b1c1操作3,类似操作2,a_1裂点,流量为次数,流向b_1到c_1
4操作4,中间新增一个过渡点,并将这个过渡点裂点,流量为次数
a1a2b1b2a_1到a_2流向这个点,这个点流向b_1到b_2
按照这样建好图直接跑最大流即可
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};

//dinic
int n,m,s,t,N;
struct edge{
	int v,nx;
	int f;
}e[200010];
int head[maxn],cnt,cur[maxn],dep[maxn];
void init(){
	cnt=0;
	for(int i=0;i<=N;i++)
		head[i]=-1;
}
void add(int u,int v,int w){
	e[cnt]={v,head[u],w};
	head[u]=cnt++;
	e[cnt]={u,head[v],0};
	head[v]=cnt++;
}
bool bfs(){
	for(int i=0;i<=N;i++)cur[i]=head[i],dep[i]=0;
	queue<int> q;
	q.push(s);dep[s]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i!=-1;i=e[i].nx){
			int v=e[i].v;
			if(e[i].f&&!dep[v]){
				dep[v]=dep[u]+1;
				if(v==t)return 1;
				q.push(v);
			}
		}
	}
	return 0;
}
int dfs(int u,int lim){
	if(u==t)return lim;
	int ans=0,v,tmp;
	for(int i=cur[u];i!=-1;i=e[i].nx){
		cur[u]=i;
		v=e[i].v;
		if(dep[v]==dep[u]+1&&e[i].f){
			tmp=dfs(v,min(lim,e[i].f));
			e[i].f-=tmp;
			e[i^1].f+=tmp;
			ans+=tmp;
			lim-=tmp;
			if(!lim)break;
		}
	}
	if(!ans||lim)dep[u]=0;
	return ans;
}
int dinic(){
	int ans=0;
	while(bfs())ans+=dfs(s,inf);
	return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    while(_--){
        int n,m,k;
        cin>>n>>m>>k;
        s=0,N=k+2*m+1,t=k;
        init();
        for(int i=1,x;i<=n;i++){
            cin>>x;
            add(s,x,1);
        }
        for(int i=1;i<=m;i++){
            int op,c;
            cin>>op>>c;
            if(op==1){
                int a,b;
                cin>>a>>b;
                add(a,b,c);
            }
            if(op==2){
                int a1,a2,b;
                cin>>a1>>a2>>b;
                for(int j=a1;j<=a2;j++)
                    add(j,k+i,c);
                add(k+i,b,c);
            }
            if(op==3){
                int a,b1,b2;
                cin>>a>>b1>>b2;
                for(int j=b1;j<=b2;j++)
                    add(k+i,j,c);
                add(a,k+i,c);
            }
            if(op==4){
                int a1,a2,b1,b2;
                cin>>a1>>a2>>b1>>b2;
                for(int j=a1;j<=a2;j++)
                    add(j,k+i,c);
                add(k+i,k+i+m,c);
                for(int j=b1;j<=b2;j++)
                    add(k+i+m,j,c);
            }
        }
        cout<<dinic()<<endl;
    }
    return 0;
}


I.字典序

题意:
na给定一个长度为n的数组a
isi将去掉第i个元素后的数组写为s_i
sii将s_i按字典序排序,并依次输出i
(i)(字典序相同i从小到大输出)
题解:
对于一个数,如果大于下一个数,去掉后,字典序变小
如果等于,不论去掉哪个都是字典序都是相同的
如果小于下一个数,去掉后字典序变大

所以需要做的就是,先对数进行去重,并记录相同数的左右位置
由于从左往右,第一个大于的数字典序是最大的
第一个小于的字典序是最小的
所以我们倒着走,这就很类似堆栈了
()如果遇到小于下一个数的往右放,否则往左放(左到右字典序增大)
这个放的是位置,如果这个数没有相同数,刚记录的左右位置相等
这个位置也是他原本的位置
如果这个数有相同的数,从刚记录的左位置输出到右位置
这样最终放完的这个顺序就是答案
可以想一下,往左放往右放的数据结构
stldeque最好用的明显就是stl的deque,可以实现这一操作
AC代码

/*
    Author:zzugzx
    Lang:C++
    Blog:blog.csdn.net/qq_43756519
*/
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};

int l[maxn],r[maxn],a[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int _;
    cin>>_;
    while(_--){
        int n;cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        int cnt=0;
        for(int i=1;i<=n;i++){
            int j=i;
            while(a[j+1]==a[j]&&j+1<=n)j++;
            a[++cnt]=a[i],l[cnt]=i,r[cnt]=j;
            i=j;
        }
        a[cnt+1]=0;deque<int> q;
        for(int i=cnt;i;i--)
            if(a[i]>a[i+1])q.push_front(i);
            else q.pb(i);
        for(auto i:q)
            for(int j=l[i];j<=r[i];j++)
                cout<<j<<' ';
        cout<<endl;
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章