2023NEUQACM Week2

必做题


P1007 独木桥

题目大意:求士兵下桥的最短和最长时间。

简要思路:

  1. 欲求所走的最短路程:在桥左半边的应向左走,在桥右半边的应向右走;
  2. 欲求所走的最长路程:在桥左半边的向右走,在桥右半边的向左走。

值得注意的是题干中说两士兵相遇后会同时转向,等价于两士兵相遇后直接穿过对方!

代码如下:

#include
using namespace std;

int cor[5005];
int main()
{
    int len, num, minTime=0, maxTime=0;
    cin>>len>>num;
    for(int i=0 ; i>cor[i];
    
    for(int i=0 ; i len/2){
            minTime = max(minTime, len-cor[i]+1); // 在右半边的向右走
            maxTime = max(maxTime, cor[i]); // 在右半边的向左走
        }
        else{ 
            minTime = max(minTime, cor[i]);
            maxTime = max(maxTime, len-cor[i]+1);
        }
    }
    cout << minTime << " " << maxTime;
    return 0;
}

P1223 排队接水

题目大意:已知每个人打水所需时间,求使所有人的平均等待时间最短的排队方案。

简要思路:为了使 n n n 个人的平均等待时间最小,应该让打水快的人先打,所以我们需要把人按照打水时间从低到高排列。同时,为了输出打水方案,需要开一个 struct

值得注意的是:因为我们要求的是等待时间,所以最后一个人的打水时间是不需要考虑的!

代码如下:

#include
#include
#include
using namespace std;

struct hito{
    int seq;
    int val;
};
bool cmp(hito h1, hito h2)
{
    return h1.val < h2.val;
}
hito arr[5005];
int main()
{
    int n;
    cin>>n;
    for(int i=0 ; i>arr[i].val;
        arr[i].seq = i+1;
    }
    sort(arr, arr+n, cmp);
    for(int i=0 ; i

P1803 凌乱的yyy / 线段覆盖

题目大意:已知所有比赛的开始和结束时间,求最多可参赛数。

思路:易证在最多参赛的组合中,有最小的 b m i n b_{min} bmin 的比赛一定是第一个参加的。以此类推:
∀   b k < b m a x   ,   i f    ∃   j   ϵ   N ∗ \forall \ b_k bk<bmax , if j ϵ N:
b k   <   b k + j b_k\ <\ b_{k+j} bk < bk+j
使得:
b k   ≤   a k + j b_k\ \le\ a_{k+j} bk  ak+j
认为在参加比赛 k k k 后可参加比赛 k + j k+j k+j

只要保证每次找到的 b k + j b_{k+j} bk+j 都是最小的,就能找到参赛次数最多的解,故我们要把 b i b_i bi 排序。

因为 a i a_i ai b i b_i bi 是成对的,要开一个 struct
完整代码如下:

#include
using namespace std;

struct tesuto{
    int a;
    int b;
};
bool cmp(tesuto t1, tesuto t2)
{
    return t1.b < t2.b;
}
tesuto t[1000005];
int main()
{
    // freopen("D:\\test.txt","r",stdin);
    int n, ans=1;
    cin>>n;
    for(int i=0 ; i> t[i].a >> t[i].b;
    sort(t, t+n, cmp);
    for(int i=0 ; i= t[i].b){ 
                ++ans;
                break; // 找到任意一个j就行,直接break
            }
        i = j;
    }
    cout << ans;   
    return 0;
}

P1031 [NOIP2002 提高组] 均分纸牌

题目大意:有 N N N 堆牌,你每次可以将某一堆牌的若干张移到它相邻的堆上,求使所有堆的牌数相等的最少操作数。

简要思路:设最后每堆牌的数目都是 m m m, 因为第一堆和最后一堆的牌只能分别从或向第二堆和倒数第二堆得到或放置牌,我们从两边向中间递推,保证每次最左边和最右边的牌堆等于 m m m
完整代码如下:

#include
using namespace std;

int n, m=0, ans=0;
int arr[105];
int main()
{
    cin>>n;
    for(int i=0 ; i>arr[i];
        m += arr[i];
    }
    m /= n;
    int i=0, j=n-1;
    while(i<=j){
        if(arr[i]==m || arr[j]==m){
            if(arr[i]==m) i++; // 所有小于i的牌堆已经是m了,i等于m的话就不用操作了,直接跳过i
            if(arr[j]==m) j--; // 同理
            continue;
        }
        if(i+1==j && arr[i]!=arr[j]){
            ++ans;
            break;
        }
        else if(i+2==j && arr[i]!=arr[j]){
            ans += 2;
            break;
        }
        else{
            arr[i+1] -= m-arr[i]; i++; // 牌堆里的牌可能会是负数,不过不影响
            arr[j-1] -= m-arr[j]; j--;
            ans += 2;
        }
    }
    cout << ans;
    return 0;
}

选做题


P1094 [NOIP2007 普及组] 纪念品分组

题目大意:把一组数分成若干个小组,每小组要么一个数,要么一对数。保证小组里数的和小于给定值 w w w, 求小组最少有几个

思路:我们应尽量让数成对,即对于每个 P i P_i Pi 它应与 m a x P j , (   P j ≤ w − P i ,   i < j ) maxP_j,(\ P_j\le w-P_i,\ imaxPj,( PjwPi, i<j),配对。无法配对的数单独一组。
完整代码如下:

#include
using namespace std;

int w, n, ans=0;
int arr[30005];
int main()
{
    cin>>w>>n;
    for(int i=0 ; i> arr[i];
    sort(arr,arr+n);
    int i=0, j=n-1;
    while(i<=j){
        if(arr[i]+arr[j]>w) // 说明arr[j]只能单独一组
            j--, ++ans;
        else
            i++, j--, ++ans;
    }
    cout << ans;
    return 0;
}

P1080 [NOIP2012 提高组] 国王游戏

题目大意:对于从 0 0 0 n n n n + 1 n+1 n+1 a , b a, b a,b,将 1 1 1 n n n 对排列,使 m a x ( ∏ i = 0 k − 1 a i b k ) ,   ( 1 ≤ k ≤ n ) max(\frac{\prod_{i=0}^{k-1}a_i}{b_k}),\ (1\le k\le n) max(bki=0k1ai), (1kn) 最小
分析:

  1. 首先,要找到排列的方法,不难想到按照 a i ∗ b i < a i + 1 ∗ b i + 1 a_i*b_i < a_{i+1}*b_{i+1} aibi<ai+1bi+1 的方式排列是其中一种方法。
  2. 其次,对于 100 % 100\% 100% 的数据有 1 ≤ n ≤ 1000 , 0 < a , b < 10000 1\le n\le1000,01n1000,0<a,b<10000,答案的数量级在 1000 0 1000 10000^{1000} 100001000 1 0 4000 10^{4000} 104000 必须用高精度储存答案。

下面给出 1. 1. 1. 的证明:假设我们已经按照某种规则排好了 a n , b n a_n, b_n an,bn, 设前 k k k 个大臣获得的金币数最大为 μ k \mu_k μk
μ k = m a x ( ∏ i = 0 k − 1 a i b k ) , ( 1 ≤ k ≤ n ) \mu_k=max(\frac{\prod_{i=0}^{k-1}a_i}{b_k}),(1\le k\le n) μk=max(bki=0k1ai),(1kn)
μ n \mu_n μn 的最小值:
假设有第 p + 1 p+1 p+1 和第 p + 2 p+2 p+2 个大臣,如果互换他们的位置,只会影响 μ p + 1 \mu_{p+1} μp+1 μ p + 2 \mu_{p+2} μp+2 的数值,有
μ p + 1 = m a x ( μ p , ∏ i = 0 p a i b p + 1 ) \mu_{p+1}=max(\mu_p, \frac{\prod_{i=0}^{p}a_i}{b_{p+1}}) μp+1=max(μp,bp+1i=0pai)
μ p + 2 = m a x ( μ p , ∏ i = 0 p a i b p + 1 , ∏ i = 0 p + 1 a i b p + 2 ) \mu_{p+2}=max(\mu_p, \frac{\prod_{i=0}^{p}a_i}{b_{p+1}}, \frac{\prod_{i=0}^{p+1}a_i}{b_{p+2}}) μp+2=max(μp,bp+1i=0pai,bp+2i=0p+1ai)
如果交换他们的位置,得到:
μ p + 1 ′ = m a x ( μ p , ∏ i = 0 p a i b p + 2 ) \mu'_{p+1}=max(\mu_p, \frac{\prod_{i=0}^{p}a_i}{b_{p+2}}) μp+1=max(μp,bp+2i=0pai)
μ p + 2 ′ = m a x ( μ p , ∏ i = 0 p a i b p + 2 , ∏ i = 0 p + 2 a i a p + 1 ∗ b p + 1 ) \mu'_{p+2}=max(\mu_p, \frac{\prod_{i=0}^{p}a_i}{b_{p+2}}, \frac{\prod_{i=0}^{p+2}a_i}{a_{p+1}*b_{p+1}}) μp+2=max(μp,bp+2i=0pai,ap+1bp+1i=0p+2ai)
自然有 μ p + 1 ≤ μ p + 2 , μ p + 1 ′ ≤ μ p + 2 ′ \mu_{p+1}\le \mu_{p+2},\quad\mu'_{p+1}\le \mu'_{p+2} μp+1μp+2,μp+1μp+2

如果获得了最多的金币的大臣在第 p + 1 p+1 p+1 和第 p + 2 p+2 p+2 个大臣中,则:
μ n = m a x ( μ p + 1 , μ p + 2 ) = μ p + 2 \mu_n=max(\mu_{p+1},\mu_{p+2})=\mu_{p+2} μn=max(μp+1,μp+2)=μp+2
有必要条件:
μ p + 2 ≤ μ p + 2 ′ \mu_{p+2}\le \mu'_{p+2} μp+2μp+2
若对于所有相邻的大臣 p + 1 , p + 2 p+1,p+2 p+1,p+2,令:
μ p + 2 ≤ μ p + 2 ′ \mu_{p+2}\le \mu'_{p+2} μp+2μp+2
( 那么易证对于任意的大臣间,如果交换他们,均满足 μ ≤ μ ′ \mu\le \mu' μμ,此时 μ n \mu_n μn 取最小值 )
(1) 若 μ p + 2 = μ p \mu_{p+2}=\mu_p μp+2=μp,自然有 μ p + 2 ≤ μ p + 2 ′ \mu_{p+2}\le \mu'_{p+2} μp+2μp+2
(2) 若 μ p + 2 ≠ μ p \mu_{p+2}\neq \mu_p μp+2=μp,必有
m a x ( ∏ i = 0 p a i b p + 1 , ∏ i = 0 p + 1 a i b p + 2 ) ≤ m a x ( ∏ i = 0 p a i b p + 2 , ∏ i = 0 p + 2 a i a p + 1 ∗ b p + 1 ) max(\frac{\prod_{i=0}^{p}a_i}{b_{p+1}}, \frac{\prod_{i=0}^{p+1}a_i}{b_{p+2}})\le max(\frac{\prod_{i=0}^{p}a_i}{b_{p+2}}, \frac{\prod_{i=0}^{p+2}a_i}{a_{p+1}*b_{p+1}}) max(bp+1i=0pai,bp+2i=0p+1ai)max(bp+2i=0pai,ap+1bp+1i=0p+2ai)

  1. 对于 ∏ i = 0 p a i b p + 1 \frac{\prod_{i=0}^{p}a_i}{b_{p+1}} bp+1i=0pai ∏ i = 0 p a i b p + 1 < ∏ i = 0 p + 2 a i a p + 1 ∗ b p + 1 \frac{\prod_{i=0}^{p}a_i}{b_{p+1}} < \frac{\prod_{i=0}^{p+2}a_i}{a_{p+1}*b_{p+1}} bp+1i=0pai<ap+1bp+1i=0p+2ai 恒成立
  2. 对于 ∏ i = 0 p + 1 a i b p + 2 \frac{\prod_{i=0}^{p+1}a_i}{b_{p+2}} bp+2i=0p+1ai ∏ i = 0 p + 1 a i b p + 2 > ∏ i = 0 p a i b p + 2 \frac{\prod_{i=0}^{p+1}a_i}{b_{p+2}} > \frac{\prod_{i=0}^{p}a_i}{b_{p+2}} bp+2i=0p+1ai>bp+2i=0pai 恒成立,故必有 ∏ i = 0 p + 1 a i b p + 2 ≤ ∏ i = 0 p + 2 a i a p + 1 ∗ b p + 1 \frac{\prod_{i=0}^{p+1}a_i}{b_{p+2}} \le \frac{\prod_{i=0}^{p+2}a_i}{a_{p+1}*b_{p+1}} bp+2i=0p+1aiap+1bp+1i=0p+2ai,解得 a p + 1 ∗ b p + 1 ≤ a p + 2 ∗ b p + 2 a_{p+1}*b_{p+1} \le a_{p+2}*b_{p+2} ap+1bp+1ap+2bp+2

综上,所有相邻的大臣 p + 1 、 p + 2 ( 1 ≤ p + 1 < p + 2 ≤ n ) p+1、p+2(1\le p+1p+1p+2(1p+1<p+2n) 间满足
a p + 1 ∗ b p + 1 ≤ a p + 2 ∗ b p + 2 a_{p+1}*b_{p+1} \le a_{p+2}*b_{p+2} ap+1bp+1ap+2bp+2
μ n \mu_n μn 取最小值的充分条件,证毕。
ps:在洛谷上看到了几篇证明,感觉逻辑都不太融洽,自己试着证了证,如有错漏之处,敬请指正!写了一下午要累死了kuso
完整代码如下:

#include
using namespace std;
// 原谅我用罗马字写变量名
int n,a,b,tmp[4005],ans[4005];
struct hito{
    int k; // 排序的依据
    int l;
    int r;
}arr[1005];
int greatPi[4005]; // 大π表示连乘
void Input(int a, int b, int i)
{
    arr[i].l = a;
    arr[i].r = b;
    arr[i].k = a*b;
}
void kakeru(int a)
{
    int carry = 0;
    for(int i=0 ; i<4005 ; i++){
        int t = (a*greatPi[i] + carry);
        greatPi[i] = t%10;
        carry = t/10;
    }
}
bool isgreater(int a[], int b[])
{
    bool ok = 1;
    for(int i=4004 ; i>=0 ; i--){
        if(a[i] > b[i]) break;
        else if(a[i] < b[i]){
            ok = 0;
            break;
        }
    }
    return ok;
}
void waru(int t[], int b)
{
    int g[4005];
    int m = 4004;
    for(int i=0 ; i<4005 ; i++) g[i] = t[i];
    while(g[m]==0) m--;
    for(int i=m ; i>=0 ; i--){
            tmp[i] = g[i]/b;
        g[i-1] += (g[i]%b)*10;
    }
    tmp[0] = g[0]/b;
    if(isgreater(tmp, ans))
        for(int i=0 ; i<=m ; i++)
            ans[i] = tmp[i];
}
bool cmp(hito h1, hito h2)
{
    return h1.k < h2.k;
}
int main()
{
    // freopen("D:\\test.txt","r",stdin);
    cin>>n>>a>>b; 
    Input(a,b,0);
    for(int i=0 ; a>0 ; i++){
        greatPi[i] = a%10; // 初始化连乘的第一项
        a /= 10;
    }
    for(int i=1 ; i<=n ; i++){
        cin>>a>>b;
        Input(a,b,i);
    }
    sort(arr+1, arr+n+1, cmp);
    for(int i=1 ; i<=n ; i++){
        waru(greatPi, arr[i].r);
        kakeru(arr[i].l);
    }
    int m=4004;
    while(ans[m]==0 && m>0) m--;
    for(int i=m ; i>=0 ; i--) cout << ans[i];
    return 0;
}

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

思路:用小根堆写一个优先队列,每次出列2个元素,加在 ans 上,再相加后入列,直到只剩一个数,输出 ans
完整代码如下:

#include
using namespace std;
int n,ans,tmp;
int arr[10005];
int Pop(int size) // 出列
{
	int minx = arr[1];
	int x = arr[size];
	int i,j;
	for(i=1 ; i*2<=size ; i=j){
		j = i*2;
		if(j!=size && arr[j+1] x ; i/=2)
		arr[i] = arr[i/2];
	arr[i] = x;
}
int main()
{
	// freopen("D:\\test.txt","r",stdin);
	cin>>n;
	int size=0;
	for(int i=1 ; i<=n ; i++){
		cin>>tmp;
		Insert(tmp,size);
		++size;
	}
	while(size>1){
		tmp = Pop(size) + Pop(size-1);
		ans += tmp;
		Insert(tmp, size-2);
		--size;
	}
	cout << ans;
	return 0;
}

也可以用STL,实测速度只有手写的一半左右:

#include
using namespace std;
int n,tmp,ans=0;
int main()
{
	// freopen("D:\\test.txt","r",stdin);
	priority_queue,greater> pq;
	cin>>n;
	for(int i=0 ; i>tmp;
		pq.push(tmp);
	}
	while(pq.size()>1){
		tmp = pq.top();
		pq.pop();
		tmp += pq.top();
		pq.pop();
		ans += tmp;
		pq.push(tmp);
	}
	cout << ans;
	return 0;
}

P1199 [NOIP2010 普及组] 三国游戏

分析:按行来看的话,可以肯定的是每一行的最大值都是不可能取到的,而每一行第二大的值则一定能取到。如果我们把每一行的最大值和次大值都找出,那么只要先选择 有最大的次大值 的那一行,我们就可以在第二步选到这个值,接下来只要也按照电脑的逻辑,阻止它选到任何一行的最大值,我们就必胜。
完整代码如下:

#include
using namespace std;
int n,tmp,max1,max2,maxi;
int arr[505][505];
int main()
{
	// freopen("D:\\test.txt","r",stdin);
	cin>>n;
	for(int i=1; i<=n ; i++){
		for(int j=i+1 ; j<=n ;j++){
			cin>>tmp;
			arr[i][j] = arr[j][i] = tmp;
		}
	}
	for(int i=1 ; i<=n ; i++){
		for(int j=1 ; j<=n ; j++){
			if(arr[i][j]>max1){
				max2 = max1;
				max1 = arr[i][j];
			}
			else if(arr[i][j]>max2) // max2在max1的后面的情况
				max2 = arr[i][j];
        }
		maxi = max(maxi,max2);
		max1 = max2 = 0; // 记得归零
	}
	cout << 1 << '\n' << maxi;
	return 0;
}

你可能感兴趣的:(NEUQACM,算法,数据结构,贪心算法,c++)