2019_C3

A C3-Zexal的多路流水線調度

題目描述

Zexal的偶像SkyLee想要組裝一臺電腦,而電腦需要按照固定的順序進行安裝,不能把配件都買好一起安裝(因爲SkyLee只會按照順序安裝,他分不清內存條和顯卡)。

城市裏有n個電腦城,並且每個電腦城都有所有的配件賣,除了價格不同外完全一樣。一臺電腦一共有m個配件,按照安裝順序編號爲1−m。

假設第i個電腦城的編號爲j的配件售價爲p[i][j],從第i個電腦城到第j個電腦城的交通費用爲f[i][j]。

那麼SkyLee組裝好整臺電腦最少需要多少錢呢?(配件費用+交通費用)

輸入

多組數據輸入

第一行兩個整數n和m,分別爲電腦城數量和配件數量(2<n,m≤500)

接下來n行,每行m個整數,表示配件的價格p[i][j](0≤p[i][j]≤500)

接下來n行,每行n個整數,表示交通費用f[i][j](0≤f[i][j]≤500)

輸出

對於每組數據,輸出一行,爲最小費用

輸入樣例
3 3
10 1 10
8 5 10
10 10 2
0 5 2
1 0 5
1 1 0
輸出樣例
14
思路

多條流水線的ALS

代碼
#include <iostream>
#include <cstring>
using namespace std;
#define maxn 500+10
int p[maxn][maxn];//p[i][j] i電腦城的編號爲j的配件
int f[maxn][maxn];//f[i][j] 從i電腦城到j電腦城
int dp[maxn][maxn];//dp[i][j] 在i電腦城買第j個配件

int min(int a,int b){
    if(a<=b) return a;
    else return b;
}

int main(){
    int n,m;
    cin>>n>>m;
    memset(dp,0x3f,sizeof(dp));
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            cin>>p[i][j];
        }
        dp[i][0] = p[i][0];
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            cin>>f[i][j];
        }
    }

    for(int j=1;j<m;j++){//配件
        for(int i=0;i<n;i++){//電腦城
            for(int k=0;k<n;k++){//i-1在k電腦城買
                dp[i][j] = min(dp[i][j],dp[k][j-1]+f[k][i]);//把下面一句移到這裏就出錯。。。
            }
            dp[i][j]+=p[i][j];
        }
    }
    int ans = 0x3f3f3f3f;
    for(int i=0;i<n;i++){
        ans = min(ans,dp[i][m-1]);
    }
    cout<<ans<<endl;
    return 0;
}

B C3-炮彈殺傷力

題面

世界需要和平,人民嚮往和平。

但是,歷史上,很多和平都是靠戰爭換來的。

Z國和Y國開戰了,Z國已經向Y國擺好了n門炮彈,記爲x1,x2,⋯,xn,這n門炮彈是按自然順序有序擺放。開戰後,可以選擇哪些炮彈要發射,哪些不發射,發射多門炮彈時,發射的順序必須跟原始炮彈擺放的相對順序一致,但連續發射的兩門炮彈不一定在原始擺放順序中也是連續的。假設每一門炮彈的殺傷力爲1,後發射的炮彈的射程大於前面發射的炮彈,其殺傷力才能展現,否則,該門炮彈發射就不具備殺傷力。

你是該場戰爭的指揮官,如何安排炮彈的發射順序,使得殺傷力最大。

輸入

第一個數爲炮彈門數n1n25000n(1≤n≤25000)

接下來1行,包括n個正整數,第i個數表示擺放的第i門炮彈的發射射程k0k1000000k(0≤k≤1000000)

輸出

輸出一行,是一個整數,表示該場戰爭發射炮彈形成的殺傷力。

輸入樣例
3
16 10 15
輸出樣例
2
樣例解釋
發射第2門(射程爲10)和第3門(射程爲15)炮彈。
思路

考察點:二分+貪心 O(nlogn)
注意: O(N2)O(N^{2})的 DP 會超時
新建一個low數組,low[i]表示長度爲i的LIS結尾元素的最小值。
對於一個上升子序列,顯然其結尾元素越小,越有利於在後面接其他的元素,也就越可能變得更長。
我們只需要維護low數組,對於每⼀個a[i],如果a[i] > low[ans],就把a[i]接到當前最長的LIS後面,即
low[++ans]=a[i]。
如果a[i] < low[ans],我們用二分查找找到⼀個剛好大於a[i]的然後把它換掉
low數組中存的並不一定是正確LIS,但是不影響求LIS長度
讀取第n個數,連接在結尾比n小的序列上,再在滿足前面這些條件的序列中選最長的。(dp[j]+1)裏面最大的

代碼
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 25000+6
int num[N];
int dp[N];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n,res=0;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>num[i];
        dp[i] = 1;
        for(int j=0;j<i;j++){
            if(num[i]>num[j]){
                dp[i] = max(dp[i],dp[j]+1);
            }
        }
        res = max(res,dp[i]);
    }
    cout<<res;
    return 0;
}

C C3-Zexal的矩陣鏈乘

題面描述

用加括號的方式給出最優的矩陣相乘方案

輸入

多組數據輸入

第一行一個整數 n,表示矩陣鏈的長度(1<=n<=300)

接下來一行n+1個數表示這些矩陣的行數和列數

別問我爲什麼只有n+1個數,每相鄰的兩個數表示一個矩陣的大小

輸出

對於每組數據,輸出兩行,第一行爲計算次數,第二行爲計算方案,用加括號的方式給出最優的矩陣相乘方案

如果不幸最優方案不唯一,選擇優先計算左邊的矩陣

輸入樣例
3
10 30 5 60
3
10 20 5 4
輸出樣例
4500
((A1A2)A3)
1200
((A1A2)A3)
Hint

在第二組樣例中,選擇((A1A2)A3)時,結果爲10×20×5+10×5×4=1200

選擇A1(A2A3)時,結果爲20×5×4 + 10×20×4 = 1200

這時選擇第一種,優先計算左邊的!

思路

注意輸入的是n+1個數,每相鄰兩個數代表一個矩陣的大小。

而不是一對數代表一個矩陣的大小

代碼
#include <iostream>
#include <cstdio>
#include <climits>
#define maxm 303
using namespace std;

int n;
int list[maxm];
int mc[maxm][2];
int m[maxm][maxm];
int s[maxm][maxm];

void print(int i,int j){
    if(i==j){
        printf("A%d",i);
    }
    else{
        printf("(");
        print(i,s[i][j]);
        print(s[i][j]+1,j);
        printf(")");
    }
}

int main(){
    while (~scanf("%d",&n)){
        for(int i = 0;i<=n;i++){
            scanf("%d",&list[i]);
        }
        for(int i=1;i<=n;i++){
            mc[i][0]=list[i-1];
            mc[i][1]=list[i];
        }//我是垃圾,所以我還是把n+1個數強行轉化成了n對數的形式,方便好看。。
        for(int i=1;i<=n;i++) {
            m[i][i] = 0;
        }
        for(int len=2;len<=n;len++){//len爲長度
            for(int i = 1;i<=n+1-len;i++){//i爲子問題的起始點行數
                int j = i+len-1;//j爲最後一個的行數
                m[i][j] = INT_MAX;
                for(int k = j-1; k >=i; k--){//優先在右邊分割
                    int q = m[i][k]+m[k+1][j] + mc[i][0]*mc[k+1][0]*mc[j][1];
                    if(q < m[i][j]){
                        m[i][j] = q;
                        s[i][j] = k;
                    }
                }
            }
        }

        printf("%d\n",m[1][n]);
        print(1,n);
        printf("\n");
    }
    return 0;
}

助教的:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int ms = 550;
int p[ms], dp[ms][ms], s[ms][ms];
void print(int l, int r)
{
	 if (l == r) cout << "A" << l;
	 else
	 {
		 cout << "(";
		 print(l, s[l][r]);
		 print(s[l][r] + 1, r);
		 cout << ")";
	 }
}
int main()
{
	ios::sync_with_stdio(false); cin.tie(nullptr);
	int n, m;
	while (cin >> n)
	{
		memset(dp, 0x3f, sizeof(dp));
		memset(s, 0, sizeof(s));
		for (int i = 0; i <= n; ++i) cin >> p[i];
		for (int i = 0; i <= n; ++i) dp[i][i] = 0;
		for (int i = 1; i < n; ++i)
		{
			for (int j = 1; j + i <= n; ++j)
			{
				for (int k = j; k < j + i; ++k)
				{
					int now = dp[j][k] + dp[k + 1][j + i] + p[j - 1] * p[k] * p[j + i];
					if (now <= dp[j][j + i])
					{
						s[j][j + i] = k;
						dp[j][j + i] = now;
					}
				}
			}
 		}
		cout << dp[1][n] << "\n";
		print(1, n);
		cout << "\n";
	}
	return 0;
}

D C3-Zexal的OBST

題目描述

假設給定一個n個不同關鍵字的嚴格升序序列K=<k[1], k[2], …, k[n]>,用這些關鍵字構造二叉搜索樹。對關鍵字k[i],有p[i]次被檢索到。有些搜索的值可能不在K中,假設n+1個僞關鍵字D=<d[0], d[1], …, d[n]>,對i=1, 2, …, n-1,d[i]表示在k[i]和k[i+1]之間的值,d[0]表示小於k[1]的值,d[n]表示大於k[n]的值。對每個僞關鍵字d[i],有q[i]次被檢索到。請注意這裏規定了每個關鍵字和僞關鍵字的檢索次數。

用上述D序列作葉節點,K序列做內部節點,(可以參考算法導論第三版中文版226-230頁,但注意題目定義的不同之處)構造一棵最優二叉搜索樹。假設根節點深度爲1,給定p, q,求出這二叉搜索棵樹的最小總代價。

總代價定義爲下面兩式之和:
i=1ndepth(k[i])×p[i]\sum\limits_{i=1}^{n} depth(k[i])\times p[i]
i=0ndepth(d[i])×q[i]\sum\limits_{i=0}^{n} depth(d[i])\times q[i]

輸入

第一行兩個整數n。1n5001 \le n \le 500

第二行nn個整數 p[i]p[i],表示關鍵字的出現次數。

第三行n+1n+1個整數q[i]q[i],表示i1i-1關鍵字與ii關鍵字之間的出現次數。0p[i],q[i]10000≤p[i],q[i]≤1000

輸出

一個整數,表示最小總代價

輸入樣例
5
15 10 5 10 20
5 10 5 5 5 10
輸出樣例
275
思路

最優二叉搜索樹

代碼
/*
 * 最優二叉搜索樹
 * 每個節點的深度加一,該子樹的期望搜索的cost增量爲該子樹所有節點的搜索概率之和
 */
#include<iostream>
using namespace std;
#define N 500+6
#define INF 0x3f3f3f3f
int n;
int dep;
int p[N];
int q[N];
int e[N][N];//記錄cost
int w[N][N];//記錄增加的cost
int r[N][N];//記錄i,j之間的劃分

void PRINT_OPTIONAL_BST(int i,int j,int depth){
    if(i==1&&j==n){
        dep+=p[r[i][j]]*1;
    }
    if(i==j){
        //cout<<"d"<<i-1<<" is the left child of k"<<i<<endl;
        dep+=depth*q[i-1];
        //cout<<"d"<<i<<" is the right child of k"<<i<<endl;
        dep+=depth*q[i];
    }
    else if(r[i][j]==i){
        //cout<<"d"<<i-1<<" is the left child of k"<<i<<endl;
        dep+=depth*q[i-1];
        //cout<<"k"<<r[i+1][j]<<" is the right child of k"<<r[i][j]<<endl;
        //該節點的左側爲僞關鍵字,右側爲關鍵字i+1
        dep+=depth*p[r[i+1][j]];
        PRINT_OPTIONAL_BST(i+1,j,depth+1);
    }
    else if(r[i][j]==j){
        //cout<<"d"<<i-1<<" is the left child of k"<<i<<endl;
        dep+=depth*q[j];
        //cout<<"k"<<r[i+1][j]<<" is the right child of k"<<r[i][j]<<endl;
        dep+=depth*p[r[i][j-1]];
        PRINT_OPTIONAL_BST(i,j-1,depth+1);
    }
    else{
        //cout<<"k"<<r[i][r[i][j]-1]<<" is the left child of k"<<r[i][j]<<endl;
        dep+=depth*p[r[i][r[i][j]-1]];
        PRINT_OPTIONAL_BST(i,r[i][j]-1,depth+1);
        //cout<<"k"<<r[r[i][j]+1][j]<<" is the right child of k"<<r[i][j]<<endl;
        dep+=depth*p[r[r[i][j]+1][j]];
        PRINT_OPTIONAL_BST(r[i][j]+1,j,depth+1);
    }
}

void OPTIONAL_BST(){
    for(int i=1;i<=n+1;i++){
        e[i][i-1] = q[i-1];
        w[i][i-1] = q[i-1];
    }
    for(int l=1;l<=n;l++){
        for(int i=1;i<=n-l+1;i++){
            int j=i+l-1;//區間[i,j]
            e[i][j] = INF;
            w[i][j] = w[i][j-1] + p[j] + q[j];
            for(int k=i;k<=j;k++){
                int t = e[i][k-1]+e[k+1][j] + w[i][j];
                if(t<e[i][j]){
                    e[i][j] = t;
                    r[i][j] = k;
                }
            }
        }
    }
    PRINT_OPTIONAL_BST(1,n,2);
}

int main(){
    cin>>n;
    int sum=0;
    dep = 0;
    for(int i=1;i<=n;i++){
        cin>>p[i];
        sum+=p[i];
    }
    for(int i=0;i<=n;i++){
        cin>>q[i];
        sum+=q[i];
    }
    OPTIONAL_BST();
    cout<<dep<<endl;
    return 0;
}

E C3-Zexal的浩瀚星辰

題目描述

Zexal 想要發射火箭,但是由於能源供應不足了,所以一些火箭需要延遲發射。 每個火箭每延遲一小時發射都會相應的損失。Zexal瞭解到,一共有n個火箭,其中第i個火箭原計劃在第i小時發射,即1 ~ n時間段發射,現預計k小時後電力可以恢復正常,即所有火箭將在 k+1 ~ k+n 時間段內發射,

新的火箭發射計劃不要求按照最初的發射計劃順序,唯一的要求是每個火箭都不能早於原定時間發射。請你幫忙計算一下最小的損失吧。

注意:時間均以小時爲最小單位。由於條件有限,一次只能發射一枚火箭。

輸入

輸入包含多組數據。

每組數據第一行爲正整數nnk1n500000,1k500000k(1≤n≤500000,1≤k≤500000),爲火箭總數和延遲時間。

接下來是nn個正整數 pip_{i},代表第i個火箭每延遲一小時的損失費1pi104(1≤p_{i}≤10^{4})

輸出

對於每組數據,輸出一行,爲最小的損失費用。

輸入樣例
1 2
10
2 1
10 100
輸出樣例
20
20
思路

在k+i時間能發射1~k+i號火箭
在可以發射的火箭裏選擇每小時損失最大的

自己的有問題,也不知道哪錯了

代碼

錯的:

#include<iostream>
#include<queue>
using namespace std;
#define N 500000+10
int n,k;
long long p[N];
int vis[N];
struct data{
    int num;
    long long v;
    data(int _num=0,long long _v=0):num(_num),v(_v){}
    bool operator < (const data &r)const{
        return r.v > v;
    }
};
priority_queue<data> que;
int main(){
    while (cin>>n>>k){
        long long ans=0;
        for(int i=1;i<=n;i++){
            cin>>p[i];
            if(i<=k+1){
                que.push(data(i,p[i]));
            }
            vis[i] = 1;
            ans+=k*p[i]*(k>=i?1:0);
        }

        for(int i=k+1;i<=k+n;i++){//i是實時時間
            int nu = que.top().num;
            que.pop();
            vis[nu] = 0;
            for(int j=1;j<=n;j++){
                ans+=p[j]*vis[j]*(i>=j?1:0);//判斷是否發射,是否相對原定時間有延遲
            }
            if(i<k+n) {
                que.push(data(i + 1, p[i + 1]));
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

AC的:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int ms = 500010;
long long p[ms];
int main()
{
    //ios::sync_with_stdio(false); cin.tie(nullptr);
    int n, m;
    while (cin >> n >> m)
    {
        for (int i = 0; i < n; ++i){
            cin >> p[i];
        }
        priority_queue<pair<long long, long long>> q;
        int l = 0;
        long long ans = 0;
        for (int i = 0; i < n; ++i)
        {
            while (l <= i + m && l < n) q.push(make_pair(p[l], l)), l++;
            ans += q.top().first *(i + m - q.top().second);
            q.pop();
        }
        cout << ans <<endl;
    }
    return 0;
}

F C3-排座位

題目描述

將n個女生和m個男生排成一列,要求是連續的女生不能超過x,連續的男生不能超過y
請問:一共有多少種排座位的方式(結果對1000007取模)。

注:所有的女生看作相同,所有男生看做相同。

注意內存限制。

輸入

多組數據輸入,數據組數小於20

每組數據包括四個正整數nmxy1n,m2000,1x,y2000n、m、x、y(1≤n,m≤2000,1≤x,y≤2000),含義見描述

輸出

對於每組數據,輸出一行,爲排座位的方法數(結果對1000007取模)

輸入樣例
1 2 1 1
2 3 1 2
輸出樣例
1
5
Hint
1表示女生,0表示男生

第一組數據,方法只有一種,即:010

第二組數據,方法有五種,即:01011,01101,10101,10110,11010
思路

dp[i][j][k]:已經有i個女生j個男生參與排列,第三維k是標誌位(0代表女生,1代表男生)的排列方法數。
狀態轉移方程變爲:
𝑑𝑝[𝑖][𝑗][0]=∑(𝑑𝑝[𝑖−𝑘][𝑗][1]);其中 𝑘∈[1,𝑚𝑖𝑛(𝑖,𝑥)]。
同理,𝑑𝑝[𝑖][𝑗][1]=∑(𝑑𝑝[𝑖][𝑗−𝑘][0]);其中 𝑘∈[1,𝑚𝑖𝑛(𝑗,𝑦)]。
相信你很快就能看懂,這裏用 𝑑𝑝[𝑖−𝑘][𝑗][1] 來代表最後有k個0,相當於同時把三四維合併了,巧妙至極。具體可見最優參考代碼。

代碼
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod = 1000007;
const int N = 2010;
const int M = 2010;
int a[N][M];//a[i][j]表示以女生結尾並且有i個女生前提下,1個男 到 j個男 j種方案方案總和
int b[N][M];//b[i][j]表示以男生結尾並且有j個男生前提下,1個女 到 i個女 i種方案方案總和
int n, m, x, y;
int main()
{
	while (scanf("%d%d%d%d", &n, &m, &x, &y) != EOF)
	{
	memset(a, 0, sizeof(a));
	memset(b, 0, sizeof(b));
	for (int i = 1; i <= x; i++)
		a[i][0] = 1;//初始化只有女生的情況
	for (int j = 1; j <= y; j++)
		b[0][j] = 1;//初始化只有男生的情況
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
		{
			if (i <= x)
			{
				a[i][j] = (a[i][j - 1] + b[i - 1][j]) % mod;
			//當i<=x時,b[i-1][j]就等於以女生結尾,有i個男生j個女生
			}
			else
			{
				a[i][j] = (a[i][j - 1] + b[i - 1][j] - b[i - x - 1][j] + mod) % mod;
				/* [i - 1][j] - b[i - x - 1][j] 代表在有j個男生的並且以男生結尾的前提下,有i-x個女生到i-1個女生的方案總數。
				 * 這個方案總數恰好等於以女生結尾,有i個女生j個男生的方案書。
				 */
			}
			if (j <= y)
				b[i][j] = (b[i - 1][j] + a[i][j - 1]) % mod;
			else
				b[i][j] = (b[i - 1][j] + a[i][j - 1] - a[i][j - y - 1] + mod) % mod;
		}
		int out = (a[n][m] - a[n][m - 1] + b[n][m] - b[n - 1][m] + mod + mod) % mod;
		printf("%d\n", out);
	}
	return 0;
}

等差數列

題目描述

現有一數字序列,從中取出一些數字元素,就可以組成一個等差數列,我們想知道這個等差數列最多能有多少個元素,原序列每個元素最多隻能取一次。

輸入

輸入包括多組數據。

每組數據第一行爲整數n,表示輸入序列的元素個數(3≤ n ≤10^4).

接下來一行是n個不同的正整數Ai(1≤ Ai ≤10^9)。

輸出

對於每組數據,輸出一行,爲最長的等差數列的長度(元素個數)。

輸入樣例
3
1 2 3
輸出樣例
3
HINT1

等差數列:對於數列{An},若滿足 A(n)−A(n−1)=d(n≥2) , 則稱該數列爲等差數列。其中公差d爲一常數,n爲正整數。即最短的等差數列長度爲2。

HINT2

注意:內存限制。你注意到n並不是很大嗎?需要比int更小的short int。

HINT3

你被誤導了嗎?再仔細看一次題哦,我可沒說這是一道子序列題目~

思路

應該是從中間向兩邊擴展

多個Hint自然不簡單
易錯點1:二維int數組MLE,明顯會超過內存限制,由於푛最⼤爲1e4,那麼我們的dp數組最大也是1e4,
考慮使用short int。
易錯點2:被題目開始的序列描述誤導,題目沒有要求等差數列中數字順序和輸入順序一致,所以可以先
將數組排序。
我們設置dp[i][j]表示以A[i]、A[j]開頭的等差數列(可保證i<j)。初始化值爲2。
狀態轉移:固定j,i與k分別向兩邊擴展,當2*A[j]=A[i]+A[k]時,說明A[i]、A[j]、A[k]可以組成等差數列。則有:dp[i][j]=dp[j][k]+1。

代碼
#include <cstdio>
#include <iostream>
#include <algorithm>
#define MaxSize 10005
using namespace std;

int n, ans;
int A[MaxSize];
short int dp[MaxSize][MaxSize];

int main()
{
	while(~scanf("%d", &n))
	{
		for (int i = 0; i < n; ++i)
			scanf("%d", &A[i]);
		sort(A, A+n);
		
		for (int i = 0; i < n; ++i)
			for (int j = i+1; j < n; ++j)
				dp[i][j] = 2;
		
		ans = 2;
		for (int j = n-2; j > 0; --j) {
			int i = j-1, k = j+1;
			while(i>=0 && k<n)
			{
				if(A[i]+A[k] < 2*A[j])
					k++;
				else if(A[i]+A[k] > 2*A[j])
					i--;
				else
				{
					dp[i][j] = dp[j][k] + 1;
					if(dp[i][j] > ans)
						ans = dp[i][j];
					i--, k++;
				}
			}
		}
		printf("%d\n", ans);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章