“科林明倫杯”哈爾濱理工大學第十屆程序設計競賽(同步賽)題解

題目鏈接

按難度順序

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條直線,問最多有多少個交點
題解:
nn(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爲匯點
1kk超級源點指向每個高度流量爲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;
}

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