文章目錄
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,後發射的炮彈的射程大於前面發射的炮彈,其殺傷力才能展現,否則,該門炮彈發射就不具備殺傷力。
你是該場戰爭的指揮官,如何安排炮彈的發射順序,使得殺傷力最大。
輸入
第一個數爲炮彈門數。
接下來1行,包括n個正整數,第i個數表示擺放的第i門炮彈的發射射程。
輸出
輸出一行,是一個整數,表示該場戰爭發射炮彈形成的殺傷力。
輸入樣例
3
16 10 15
輸出樣例
2
樣例解釋
發射第2門(射程爲10)和第3門(射程爲15)炮彈。
思路
考察點:二分+貪心 O(nlogn)
注意: 的 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,求出這二叉搜索棵樹的最小總代價。
總代價定義爲下面兩式之和:
輸入
第一行兩個整數n。
第二行個整數 ,表示關鍵字的出現次數。
第三行個整數,表示關鍵字與關鍵字之間的出現次數。
輸出
一個整數,表示最小總代價
輸入樣例
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 時間段內發射,
新的火箭發射計劃不要求按照最初的發射計劃順序,唯一的要求是每個火箭都不能早於原定時間發射。請你幫忙計算一下最小的損失吧。
注意:時間均以小時爲最小單位。由於條件有限,一次只能發射一枚火箭。
輸入
輸入包含多組數據。
每組數據第一行爲正整數和,爲火箭總數和延遲時間。
接下來是個正整數 ,代表第i個火箭每延遲一小時的損失費。
輸出
對於每組數據,輸出一行,爲最小的損失費用。
輸入樣例
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
每組數據包括四個正整數,含義見描述
輸出
對於每組數據,輸出一行,爲排座位的方法數(結果對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);
}
}