水题争霸赛,然而我最菜\(qwq\)
T1 导航
【题目描述】:
约翰在他的新车上装了两个导航系统\((GPS)\),但这两个\(GPS\)选择的导航线路常常不同,约翰很是恼火。
约翰所在的小镇地图由\(N\)个路口和\(M\)条单向道路构成,两个路口间可能有多条道路相连。约翰的家在\(1\)号路口,他的农场在\(N\)号路口。约翰从家出发,可以经过一系列的道路,最终到达农场。
两个\(GPS\)用的都是上述地图,但是,它们计算时间的算法不同。比如,经过第\(i\)条道路,\(1\)号\(GPS\)计算出的时间是\(P_i\)分钟,而\(2\)号\(GPS\)算出的时间是\(Q_i\)分钟。
约翰想要驾车从家到农场,但是,如果一个\(GPS\)认为约翰当前行走的这条路不在它算出的最短路径中,该\(GPS\)就会大声抱怨约翰走错了路。更倒霉的是,有可能两个\(GPS\)会同时抱怨约翰当前走的路不是它们推荐的。
请帮助约翰计算,从家到农场过程中,选择怎样的路径才能使得\(GPS\)抱怨的次数最少,请算出这个最少的抱怨次数。如果一条路上两个\(GPS\)都在抱怨,算两次\((+2)\)抱怨。
【输入格式】:
第\(1\)行: 两个空格间隔的整数:\(N\)和\(M\)
接下来\(M\)行,每行描述一条道路。第\(i\)行描述第\(i\)条道路,由四个空格间隔的整数构成,\(A_i\),\(B_i\),\(P_i\),\(Q_i\),分别表示该条道路的起点、终点、\(1\)号\(GPS\)计算的耗时、\(2\)号\(GPS\)计算的耗时。
【输出格式】:
第\(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\),分别用\(GPS1\)和\(GPS2\)作为边权,得到两个\(dis\)数组。
对于两次\(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;
}
我还是太弱了。。