第十三次考试
Posted lajioj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十三次考试相关的知识,希望对你有一定的参考价值。
水题争霸赛,然而我最菜(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;
}
我还是太弱了。。
以上是关于第十三次考试的主要内容,如果未能解决你的问题,请参考以下文章