第十三次考試
水題爭霸賽,然而我最菜\(qwq\)
T1 導航
【題目描述】:
約翰在他的新車上裝了兩個導航系統\((GPS)\),但這兩個\(GPS\)選擇的導航線路常常不同,約翰很是惱火。
約翰所在的小鎮地圖由\(N\)個路口和\(M\)條單向道路構成,兩個路口間可能有多條道路相連。約翰的家在\(1\)號路口,他的農場在\(N\)號路口。約翰從家出發,可以經過一系列的道路,最終到達農場。
兩個\(GPS\)用的都是上述地圖,但是,它們計算時間的算法不同。比如,經過第\(i\)條道路,\(1\)號\(GPS\)計算出的時間是\(P_i\)分鐘,而\(2\)號\(GPS\)算出的時間是\(Q_i\)分鐘。
約翰想要駕車從家到農場,但是,如果一個\(GPS\)
請幫助約翰計算,從家到農場過程中,選擇怎樣的路徑才能使得\(GPS\)抱怨的次數最少,請算出這個最少的抱怨次數。如果一條路上兩個\(GPS\)都在抱怨,算兩次\((+2)\)抱怨。
【輸入格式】:
第\(1\)行: 兩個空格間隔的整數:\(N\)和\(M\)
接下來\(M\)行,每行描述一條道路。第\(i\)行描述第\(i\)條道路,由四個空格間隔的整數構成,\(A_i\),\(B_i\),\(P_i\),\(Q_i\),分別表示該條道路的起點、終點、\(1\)
【輸出格式】:
第\(1\)行: \(1\)個整數, 表示所求答案。
【樣例輸入】:
5 7
3 4 7 1
1 3 2 20
1 4 17 18
4 5 25 3
1 2 10 1
3 5 4 14
2 4 6 5
【樣例輸出】:
1
【數據範圍】:
對於\(30\%\) 的數據,\(1<= N <=20\),\(1<= M <=20\)
對於\(100\%\)的數據,\(1<= N <=10000\) ,\(1 <= M <= 50000\) ,\(0<=Pi,Qi<=100000\)
思路:
因為每到一個新的節點後,原來所計算出來的最短路可能會被更新,所以應該比較自然地想到先反向建邊,求出\(n\)
對於兩次\(spfa\),記錄兩個\(pre\)數組記錄路徑,那麽對於這兩條路徑,進行如下處理:
- 如果存在\(u,v\),它們既在\(pre1\)中,又在\(pre2\)中,顯然經過這條路徑時,兩個\(GPS\)都不會抗議,就連一條權為\(0\)的邊
- 如果存在\(u,v\),它們僅在\(pre1\)中,或僅又在\(pre2\),顯然經過這條路徑時,其中一個\(GPS\)會抗議,就連一條權為\(1\)的邊,
- 對於其他路徑,顯然兩個\(GPS\)都會抗議,就連一條權為\(2\)的邊
然後再跑一邊\(spfa\),最短路的值即為答案。
這麽水我居然只有80
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
int n,m;
const int MAXM = 50005;
const int MAXN = 10005;
struct edge{
int u,v,w1,w2,nxt,w;
}e[MAXM];int head[MAXN];int cnt=0;int dis1[MAXN],dis2[MAXN];int r[MAXN];int pre1[MAXN],pre2[MAXN];int tnt=0;
struct EDGE{
int u,v,w,nxt;
}a[MAXM];int dis[MAXN];int last[MAXN];
inline void add(int u,int v,int w){
a[++tnt].u = u;a[tnt].v = v;a[tnt].w = w;a[tnt].nxt = last[u];last[u] = tnt;
}
inline void add(int u,int v,int w1,int w2){
e[++cnt].u = u;e[cnt].v = v;e[cnt].w1 = w1;e[cnt].w2 = w2;e[cnt].nxt = head[u];head[u] = cnt;
}
queue<int>q;
inline void spfa1(){
q.push(n);memset(dis1,inf,sizeof dis1);
dis1[n] = 0;
while(!q.empty()){
int u = q.front();q.pop();r[u] = 0;
for(int i=head[u];i;i=e[i].nxt){
int v = e[i].v;
if(dis1[v] > e[i].w1 + dis1[u]){
pre1[v] = i;
dis1[v] = e[i].w1 + dis1[u];
if(!r[v]){
r[v] = 1;
q.push(v);
}
}
}
}
}
inline void spfa2(){
q.push(n);memset(dis2,inf,sizeof dis2);memset(r,0,sizeof r);
dis2[n] = 0;
while(!q.empty()){
int u = q.front();q.pop();r[u] = 0;
for(int i=head[u];i;i=e[i].nxt){
int v = e[i].v;
if(dis2[v] > e[i].w2 + dis2[u]){
pre2[v] = i;
dis2[v] = e[i].w2 + dis2[u];
if(!r[v]){
r[v] = 1;
q.push(v);
}
}
}
}
}
inline void spfa(){
q.push(1);memset(dis,inf,sizeof dis);memset(r,0,sizeof r);
dis[1] = 0;
while(!q.empty()){
int u = q.front();q.pop();r[u] = 0;
for(int i=last[u];i;i=a[i].nxt){
int v = a[i].v;
if(dis[v] > a[i].w + dis[u]){
dis[v] = a[i].w + dis[u];
if(!r[v]){
r[v] = 1;
q.push(v);
}
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
int u,v,w1,w2;scanf("%d%d%d%d",&u,&v,&w1,&w2);
add(v,u,w1,w2);
add(u,v,2);
}
spfa1();
spfa2();
for(int i=1;i<=n;++i){
if(pre1[i] == pre2[i]) a[pre1[i]].w=0;
if(pre1[i] != pre2[i]){
a[pre1[i]].w = a[pre2[i]].w = 1;
}
}
spfa();
printf("%d",dis[n]);
return 0;
}
T2 比賽
【題目描述】:
有三個小夥伴組隊去參加 \(ACM\) 比賽,這場比賽共有\(n\)道題目,他們的比賽策略是這樣的:每個隊員都會對題目通看一遍,然後對每個題的難度進行估算,難度範圍為 \(1-9\)。當然,由於每個隊員的水平和特點, 他們對同一道題的估算不一定相同。
接下來他們會對所有題目進行分配。三個人分配的題目剛好是所有題目,且不會有交集,而且每個人分配的題目的編號必須是連續的,每人至少要 分一道題。請問,如何分配題目可以使得三個人拿到的題目的難度之和最小。每個人對自己 分配到的題目只按自己的估算值求和。
【輸入格式】:
第一行一個數 \(n\),表示題目的數量。
接下來有 \(3\) 行,每行表示一個學生,每行有 \(n\) 個數,表示該生對\(n\)道題的估算難度,難度介於 \([1,9]\)。
【輸出格式】:
一個整數。表示最小的估算難度之和。
【樣例輸入1】:
3
1 3 3
1 1 1
1 2 3
【樣例輸出1】:
4
【樣例輸出2】:
5
4 1 5 2 4
3 5 5 1 1
4 1 4 3 1
【樣例輸出2】:
11
【數據範圍】:
對於 \(20\%\) 的數據:\(3 <= N <= 1000\)
對於 \(100\%\) 的數據:\(3 <= N <= 200000\)
思路:
先放個正解的思路吧,我雖然\(AC\)了但是是歪門邪道。。
考點:前綴和
序列分成三段,三個人可以分別挑一段,總的方案數是排列數\(A_3^3\),於是有\(6\)種情況需要討論。
對每種情況分別考慮,預處理每個人的前綴和:\(SumX[]\),\(SumY[]\),\(SumZ[]\)
假設x,y,z三個人分別做\([1,j], [j+1,i], [i+1,n] 1<=j<i<n\)
\(Total=(SumX[j]-0)+(SumY[i]-SumY[j])+(SumZ[n]-SumZ[i])\)
\(=SumZ[n]+(SumX[j]-SumY[j])+(SumY[i]-SumZ[i])\)
\(SumZ[n]\)是一個定值,我們只需討論\((SumY[i]-SumZ[i])\)與\((SumX[j]-SumY[j])\)的關系
從左到右枚舉\(i\),記錄i左邊出現過的最小的\((SumX[j]-SumY[j]) 1<=k<i\),更新答案
討論一遍的時間復雜度為\(O(n)\),總共討論\(6\)次,所以總的時間復雜度為\(O(6n)\)
總時間復雜度為$O(6n) $
\(std\):
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
inline void _read(long long &d)
{
char t=getchar();bool f=false;
while(t<'0'||t>'9'){if(t=='-')f=true;t=getchar();}
for(d=0;t>='0'&&t<='9';t=getchar())d=d*10+t-'0';
if(f==true)d=-d;
}
long long ans=9999999999;
long long n;
long long sum[3][200005];
long long f[6][200005];
long long derta[6][200005];
void prepare()
{
long long a=1;
derta[0][a]=sum[0][a]-sum[1][a];
derta[1][a]=sum[1][a]-sum[0][a];
derta[2][a]=sum[0][a]-sum[2][a];
derta[3][a]=sum[2][a]-sum[0][a];
derta[4][a]=sum[2][a]-sum[1][a];
derta[5][a]=sum[1][a]-sum[2][a];
for(a=2;a<=n;a++)derta[0][a]=min(derta[0][a-1],sum[0][a]-sum[1][a]);
for(a=2;a<=n;a++)derta[1][a]=min(derta[1][a-1],sum[1][a]-sum[0][a]);
for(a=2;a<=n;a++)derta[2][a]=min(derta[2][a-1],sum[0][a]-sum[2][a]);
for(a=2;a<=n;a++)derta[3][a]=min(derta[3][a-1],sum[2][a]-sum[0][a]);
for(a=2;a<=n;a++)derta[4][a]=min(derta[4][a-1],sum[2][a]-sum[1][a]);
for(a=2;a<=n;a++)derta[5][a]=min(derta[5][a-1],sum[1][a]-sum[2][a]);
}
void dealit()
{
long long a,b,c,d,e;
for(a=2;a<=n-1;a++)
{
b=sum[1][a]+derta[0][a-1]+sum[2][n]-sum[2][a];
ans=min(b,ans);
}
for(a=2;a<=n-1;a++)
{
b=sum[1][a]+derta[4][a-1]+sum[0][n]-sum[0][a];
ans=min(b,ans);
}
for(a=2;a<=n-1;a++)
{
b=sum[0][a]+derta[3][a-1]+sum[1][n]-sum[1][a];
ans=min(b,ans);
}
for(a=2;a<=n-1;a++)
{
b=sum[0][a]+derta[1][a-1]+sum[2][n]-sum[2][a];
ans=min(b,ans);
}
for(a=2;a<=n-1;a++)
{
b=sum[2][a]+derta[5][a-1]+sum[0][n]-sum[0][a];
ans=min(b,ans);
}
for(a=2;a<=n-1;a++)
{
b=sum[2][a]+derta[2][a-1]+sum[1][n]-sum[1][a];
ans=min(b,ans);
}
}
int main()
{
long long a,b,c,d,e;
freopen("data10.in","r",stdin);
// freopen("test.out","w",stdout);
_read(n);
for(a=0;a<=2;a++)
{
for(b=1;b<=n;b++)
{
_read(c);
sum[a][b]=sum[a][b-1]+c;
}
}
prepare();
dealit();
cout<<ans;
return 0;
}
然而我。。。。。。。。。。。
仔細分析一下題目,我開始以為是二分,但是打了半個小時後覺得沒法分,重新讀題,發現這個題面可以轉化一下:
給出一個\(3*n\)的矩陣,求把它從三行,分成連續的三個區間,所得的總和最小值
有沒有點矩陣取數的感覺啊?
唯一的不同,就是轉移方程。
\(f[i][j] = min(f[i][j-1] , f[i-1][j-1]) + a[i][j]\)
然後把\(6\)種情況枚舉一遍,輸出最小值。。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;
const int MAXN = 200005;
int dif[4][MAXN];int dp[4][MAXN];
int main(){
int n;scanf("%d",&n);int ans = inf;
for(int i=1;i<=3;++i){
for(int j=1;j<=n;++j){
scanf("%d",&dif[i][j]);
}
}
//round 1
dif[1][n] += inf;dif[1][n-1] += inf;
dif[2][n] += inf;dif[1][1] -= inf;
for(int i=1;i<=3;++i){
for(int j=1;j<=n;++j){
dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
}
}
ans = min(ans , dp[3][n]+inf);
dif[1][n] -= inf;dif[1][n-1] -= inf;
dif[2][n] -= inf;dif[1][1] += inf;
//round 2
memset(dp,0,sizeof dp);
swap(dif[2] , dif[3]);
dif[1][n] += inf;dif[1][n-1] += inf;
dif[2][n] += inf;dif[1][1] -= inf;
for(int i=1;i<=3;++i){
for(int j=1;j<=n;++j){
dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
}
}
ans = min(ans , dp[3][n]+inf);
dif[1][n] -= inf;dif[1][n-1] -= inf;
dif[2][n] -= inf;dif[1][1] += inf;
//round 3
memset(dp,0,sizeof dp);
swap(dif[1] , dif[3]);
dif[1][n] += inf;dif[1][n-1] += inf;
dif[2][n] += inf;dif[1][1] -= inf;
for(int i=1;i<=3;++i){
for(int j=1;j<=n;++j){
dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
}
}
ans = min(ans , dp[3][n]+inf);
dif[1][n] -= inf;dif[1][n-1] -= inf;
dif[2][n] -= inf;dif[1][1] += inf;
//round 4
memset(dp,0,sizeof dp);
swap(dif[2] , dif[3]);
dif[1][n] += inf;dif[1][n-1] += inf;
dif[2][n] += inf;dif[1][1] -= inf;
for(int i=1;i<=3;++i){
for(int j=1;j<=n;++j){
dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
}
}
ans = min(ans , dp[3][n]+inf);
dif[1][n] -= inf;dif[1][n-1] -= inf;
dif[2][n] -= inf;dif[1][1] += inf;
//round 5
memset(dp,0,sizeof dp);
swap(dif[1] , dif[3]);
dif[1][n] += inf;dif[1][n-1] += inf;
dif[2][n] += inf;dif[1][1] -= inf;
for(int i=1;i<=3;++i){
for(int j=1;j<=n;++j){
dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
}
}
ans = min(ans , dp[3][n]+inf);
dif[1][n] -= inf;dif[1][n-1] -= inf;
dif[2][n] -= inf;dif[1][1] += inf;
//round 6
memset(dp,0,sizeof dp);
swap(dif[2] , dif[3]);
dif[1][n] += inf;dif[1][n-1] += inf;
dif[2][n] += inf;dif[1][1] -= inf;
for(int i=1;i<=3;++i){
for(int j=1;j<=n;++j){
dp[i][j] = min(dp[i][j-1] , dp[i-1][j-1]) + dif[i][j];
}
}
ans = min(ans , dp[3][n]+inf);
dif[1][n] -= inf;dif[1][n-1] -= inf;
dif[2][n] -= inf;dif[1][1] += inf;
printf("%d",ans);
return 0;
}
T3 澆花
【題面描述】:
\(n\) 個非負整數排成一行,每個數值為$ A_i$,數的位置不可改變。需要讓所有的數都恰好等於 \(h\)。可進行的操作是:對任意長度的區間\([i,j]\)中的每個數都加$ 1\(,\)i$ 和 \(j\) 也任選,但要求每個數只能作為一次區間的起點,也只能作為一次區間的終點。也即是說: 對任意的兩個區 間\([L_1,R_1]\) 和 \([L_2,R_2]\), 要求:\(L1≠L2\) 並且 \(R1 ≠ R2\).
請問有多少種不同的方式,使所有的數都等於 \(h\).
輸出答案模 \(1000000007 (10^9+7)\)後的余數。 兩種方式被認為不同,只要兩種方式所實施的操作的區間集合中,有一個區間不同即可。
【輸入格式】:
第 \(1\)行:\(2\) 個整數 \(n, h\)
接下來$n $行,每行 \(1\) 個整數,表示\(A_i\)
【輸出格式】:
第 \(1\)行:\(1\) 個整數,表示答案。
【樣例輸入1】:
3 2
1 1 1
【樣例輸出1】:
4
【樣例輸入2】:
5 1
1 1 1 1 1
【樣例輸出2】:
1
【樣例輸入3】:
4 3
3 2 1 1
【樣例輸出3】:
0
【數據範圍】:
\(30\%\)的數據, \(1≤n, h≤30\)
\(100\%\)的數據,\(1≤n, h≤2000 1≤Ai≤2000\)
思路:
題解用的區間\(dp\),但還有更優秀的解法。
題解:
考點 區間動態規劃
類似於括號\(dp\)的討論方式,討論\(i\)的左邊,選哪個數字作為區間的起點,更新\(i\)的值
\(dp[i][k]\)表示從左往右討論到第\(i\)個數字,\(i\)的左邊有\(k\)個數字還未被用過(被當做區間的左起點), 的方案數。
分兩種情況討論:
情況\(1\):\(i\)被別人更新(因為i前面的k個數,任選一個為區間起點,都可更新到\(i\)):
若\(a[i]+k==h\) 則$dp[i][k]=dp[i+1][k-1]*k+dp[i+1][k] $
說明,條件\(a[i]+k==h\),因為\(i\)左邊有\(k\)個數字還沒用過,那麽以這\(k\)個數字作為區間左起點可以操作\(k\)次,每次都可以更新到\(i\),更新\(k\)次,恰好就能使\(a[i]\)變成\(h\)。
現在對於\(i\)而言,有兩種選擇, 使用\(i\)或者不使用\(i\)。
若用\(i\)作為區間右端點,因為i只能當一次區間終點,所以只能從前\(k\)個中選一個來與它配對,故有\(k\)種方案,\(k\)個數中\(i\)選了一個,對於\(i+1\)它左邊就只有\(k-1\)個未使用的數了,數量總數為\(k*dp[i+1][k-1]\) 。
註意,這裏\(i\)不能再作為區間的左端點了,這樣的話會導致\(i\)被多更新一次,高度變成\(h+1\)
若不用\(i\)作為區間端點,則方案數為\(dp[i+1][k]\)
情況\(2\):\(i\)作為區間起點去更新別人
若\(a[i]+k+1=h\)則\(dp[i][k]=dp[i+1][k]*(k+1)+dp[i+1][k+1]\)
說明,因為\(i\)前面有\(k\)個數未被當做左起點使用,全部操作都只能把\(a[i]\)更新到\(h-1\)這個高度,那麽\(i\)號點必須自己作為某區間的左起點更新一次,在更新這個區間的同時把自己的高度也更新\(1\),達到\(h\)。
這樣,對於下一個數\(i+1\)而言,算上\(i\)號點,它左側有\(k+1\)個點可選做區間左端點,任選一個選後剩下\(k\)個點,狀態\(dp[i+1][k]\)
若不用\(i\)作為區間左端點,則方案數為\(dp[i+1][k+1]\)
時間復雜度\(O(n^2)\),實現時采用記憶化搜索比較方便。
#include<iostream>
#include<cstdio>
using namespace std;
#define MOD 1000000007
long long dp[2010][2010];
int a[2010];
inline void add(long long &a,long long b){
a += b;
a %= MOD;
}
int main()
{
int n,h;
cin >> n >> h;
for (int i = 1; i <= n ;i ++)
cin >> a[i];
dp[1][0] = (a[1] == h || a[1] + 1 == h?1:0);
dp[1][1] = (a[1] + 1 == h?1:0);
for (int i = 2;i <= n + 1; i ++)
for (int open = max(0,h - a[i]- 1); open <= min(h - a[i],i) ; open ++){
if (a[i] + open == h){
add(dp[i][open] , dp[i - 1][open]);
if (open > 0)
add(dp[i][open] , dp[i - 1][open - 1]);
}
if (open + a[i] + 1 == h){
add(dp[i][open] , dp[i - 1][open + 1] * (open + 1));
add(dp[i][open] , dp[i - 1][open]);
add(dp[i][open] , dp[i - 1][open] * open);
}
}
cout << dp[n][0] << endl;
return 0;
}
什麽\(O(n^2)\),明明\(O(n)\)好不好
詳見:這個懶得寫了\(233\)
#include<cstdio>
using namespace std;
typedef long long ll;
int n,m;
const int MAXN = 2005;
const ll mod = 1e9+7;
int a[MAXN];int b[MAXN];
inline int ABS(int x){return x < 0 ? -x : x;}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
a[i] = m - a[i];
if(i == 1 && a[i] > 1) {puts("0");return 0;}
if(i == n && a[i] > 1) {puts("0");return 0;};
if(a[i] < 0) {puts("0");return 0;}
}
for(int i=1;i<=n;++i){
b[i] = a[i] - a[i-1];
if(ABS(b[i]) > 1){puts("0");return 0;}
}
ll cnt = 0;ll ans = 1;
for(int i=1;i<=n;++i){
if(b[i] == 1) cnt++;
if(b[i] == -1) ans = (ans * cnt) % mod,cnt--;
if(b[i] == 0) ans = (ans * (cnt + 1)) % mod;
}
printf("%lld",ans);
return 0;
}
我還是太弱了。。
第十三次考試