长乐培训Day2
t1 足球联赛
题目
【题目描述】
巴蜀中学新一季的足球联赛开幕了。足球联赛有n只球队参赛,每赛季,每只球队要与其他球队各赛两场,主客各一场,赢一场得3分,输一场不得分,平局两只队伍各得一分。
英勇无畏的小鸿是机房的主力前锋,她总能在关键时刻踢出一些匪夷所思的妙球。但是很可惜,她过早的燃烧完了她的职业生涯,不过作为一个能够burning的girl,
她的能力不止如此,她还能预测这个赛季所有球队的比赛结果。虽然她能准确预测所有比赛的结果,但是其实她不怎么厉害,mr.gao上数学课时她总是在sleep,因此她的脑里只有整数没有实数,
而且,她只会10以内非负整数的加法运算,因此她只有结果却无法知道谁会获得联赛的冠军。
小鸿想给冠军队伍的所有队员一个拥抱,所以她把计算结果的任务交给了你:
现在,给你一个 n*n 的矩阵表示比赛情况。第 i 行第 j 列的字母表示在第 i 只队伍在主场迎战第j只队伍的比赛情况,w 表示主队赢,l 表示主队输,d 表示平局。
现在需要你给出最后能得到小鸿拥抱的队伍编号,如有多支队伍分数最高,按字典序输出编号。
【输入格式】
第一行一个整数 n。
接下来 n 行,每行 n 个字符,表示输赢情况。
第 i 行第 i 列为 - ,因为一只队伍不可能与自己比赛。
【输出格式】
输出得分最高的队伍编号。如有多个在一行中输出,用一个空格分开。
【数据规模】
对于40%的数据,满足n<=20
对于100%的数据,满足n<=50
解析
纯模拟题,直接模拟就行了,没什么好说的,就是输出时要记得判断最高成绩相同的都要输出。
code
#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> using namespace std; int read() { int num=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { num=(num<<1)+(num<<3)+ch-'0'; ch=getchar(); } return num*w; } int n,ans[51],maxn,temp,ra[51]; char s; int main() { //freopen("soccer.in","r",stdin); //freopen("soccer.out","w",stdout); n=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { cin>>s; if(s=='w') ans[i]+=3; else if(s=='l') ans[j]+=3; else if(s=='d') { ans[i]+=1; ans[j]+=1; } } for(int i=1;i<=n;i++) if(ans[i]>maxn) { maxn=ans[i]; temp=1; ra[temp]=i; } else if(ans[i]==maxn) ra[++temp]=i; for(int i=1;i<=temp;i++) cout<<ra[i]<<" "; return 0; //fclose(stdin); //fclose(stdout); }
t2 生产
题目
【题目描述】
工厂为了生产一种复杂的产品,给各个生产部门制定了详细的生产计划。那么,就经常会有生产部门要把产品送到另一个生产部门作为原料。
这是一个注重产品质量的工厂,所以每当有产品要从a部门运到b部门时,都要先从a部门送到质量检验处,检验合格后再从质量检验处运到b部门。
有些部门之间有传送带连接,厂长想知道每次将产品从一个部门运送到另一个部门最少需要多长时间。
【输入格式】
第一行两个整数n、m,n表示部门数量,m表示传送带数量。出于方便,1号部门是质量检验处。
接下来m行,每行三个整数u、v、w,表示有一条从u部门到v部门的传送带,传送过去需要w个单位时间。注意传送带是单向的。
接下来一个整数q,表示有q次运送。
接下来q行,每行两个数a、b,表示这一次要将产品从a部门运送到b部门。
【输出格式】
输出q行,每行一个整数,表示这次运送最少需要的时间。若没有传送方案,输出-1。
【数据规模】
30%的数据,n≤100,m≤500,w=1
60%的数据,n≤100,m≤5000
另20%的数据,q=1
100%的数据,2≤n≤3000,m≤100000,2≤a,b≤n,
q≤100000,1≤u,v≤n,1≤w≤10000
有些部门之间可能有多条传送带。
工厂的员工都非常尽职尽责,他们的认真和热情决定了产品的完美,所以不必考虑产品不合格的情况。
解析
很容易看得出来这题是最短路,只不过是两条最短路:
一条是从a到1,另一条是从1到b,只需要做两遍最短路即可。
最短路推荐用dijkstra,如果用spfa的话数据大一点、出题人卡一下就炸了。
code
#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> #include <queue> using namespace std; int read() { int num=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { num=(num<<1)+(num<<3)+ch-'0'; ch=getchar(); } return num*w; } const int n=3001; const int m=100001; priority_queue< pair<int,int> > q; int n,m,qq,tot1,tot2,head1[n],ver1[m],edge1[m],next1[m],head2[n],ver2[m],edge2[m],next2[m]; long long d1[n],d2[n]; bool vist[n]; void add1(int x,int y,int z) { ver1[++tot1]=y; edge1[tot1]=z; next1[tot1]=head1[x]; head1[x]=tot1; } void add2(int x,int y,int z) { ver2[++tot2]=y; edge2[tot2]=z; next2[tot2]=head2[x]; head2[x]=tot2; } void dijkstra1() { memset(d1,0x7f7f7f7f,sizeof(d1)); memset(vist,false,sizeof(vist)); d1[1]=0; q.push(make_pair(0,1)); while(q.size()) { int x=q.top().second; q.pop(); if(vist[x]) continue; vist[x]=true; for(int i=head1[x];i;i=next1[i]) { int y=ver1[i],z=edge1[i]; if(d1[y]>d1[x]+z) { d1[y]=d1[x]+z; q.push(make_pair(-d1[y],y)); } } } } void dijkstra2() { memset(d2,0x7f7f7f7f,sizeof(d2)); memset(vist,false,sizeof(vist)); d2[1]=0; q.push(make_pair(0,1)); while(q.size()) { int x=q.top().second; q.pop(); if(vist[x]) continue; vist[x]=true; for(int i=head2[x];i;i=next2[i]) { int y=ver2[i],z=edge2[i]; if(d2[y]>d2[x]+z) { d2[y]=d2[x]+z; q.push(make_pair(-d2[y],y)); } } } } int main() { //freopen("production.in","r",stdin); //freopen("production.out","w",stdout); int u,v,w,a,b; n=read(),m=read(); for(int i=1;i<=m;i++) { u=read(),v=read(),w=read(); add1(u,v,w); add2(v,u,w); } dijkstra1(); dijkstra2(); qq=read(); for(int i=1;i<=qq;i++) { a=read(),b=read(); if(d2[a]>=0x3f3f3f3f||d1[b]>=0x3f3f3f3f) cout<<"-1"<<endl; else cout<<d2[a]+d1[b]<<endl; } return 0; //fclose(stdin); //fclose(stdout); }
t3 最短路径
题目
【题目描述】
平面内给出 n 个点,记横坐标最小的点为 a,最大的点为 b,现在小y想要知道在每个点经过一次(a 点两次)的情况下从 a 走到 b,再回到 a 的最短路径。
但他是个强迫症患者,他有许多奇奇怪怪的要求与限制条件:
1.从 a 走到 b 时,只能由横坐标小的点走到大的点。
2.由 b 回到 a 时,只能由横坐标大的点走到小的点。
3.有两个特殊点 b1 和 b2, b1 在 0 到 n-1 的路上,b2 在 n-1 到 0 的路上。
请你帮他解决这个问题助他治疗吧!
【输入格式】
第一行三个整数 n,b1,b2,( 0 < b1,b2 < n-1 且 b1 ≠ b2)。n 表示点数,从 0 到 n-1 编号,b1 和 b2 为两个特殊点的编号。
以下 n 行,每行两个整数 x、y 表示该点的坐标(0 <= x,y <= 2000),从 0 号点顺序给出。doctor gao为了方便他的治疗,已经将给出的点按 x 增序排好了。
【输出格式】
输出仅一行,即最短路径长度(精确到小数点后面 2 位)
【数据规模】
20%的数据n<=20
60%的数据n<=300
100%的数据n<=1000
解析
这道题本蒟蒻做的时候写了个暴力,果不其然,tle...
后来才知道原来这题是dp。
他是从a走到b再走回a,所以我们可以将问题转化为求两条和最小的不相交线段。
令f[i][j]表示从a到b的线段走到了i点,从b到a的线段走到了j点。
边界为:f[0][0]=0。
状态转移方程:(k=max(i,j))
k!=n-1时,
1、f[i][k]=min(f[i][k],f[i][j]+d(j,k));(k!=b1)
2、f[k][j]=min(f[k][j],f[i][j]+d(i,k));(k!=b2)
k==n-1时,
3、f[n-1][n-1]=min(f[n-1][n-1],f[i][n-1]+d(i,n-1));(i!=n-1)
4、f[n-1][n-1]=min(f[n-1][n-1],f[n-1][j]+d(j,n-1));(j!=n-1)
ps:由于是从0开始编号,所以最后一个点为n-1,当然,也可以全部+1从1开始编号(会更美观),主要看个人喜好。
code
#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> #include <queue> using namespace std; int read() { int num=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { num=(num<<1)+(num<<3)+ch-'0'; ch=getchar(); } return num*w; } const int n=1005; int n,b1,b2,k; double f[n][n],x[n],y[n]; double d(int i,int j) { return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])); } int main() { //freopen("paths.in","r",stdin); //freopen("paths.out","w",stdout); memset(f,0x7f7f7f7f,sizeof(f)); f[0][0]=0; n=read(),b1=read(),b2=read(); for(int i=0;i<n;i++) x[i]=read(),y[i]=read(); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { k=max(i,j); if(k!=n-1) { k++; if(k!=b1) f[i][k]=min(f[i][k],f[i][j]+d(j,k)); if(k!=b2) f[k][j]=min(f[k][j],f[i][j]+d(i,k)); } else { if(i!=n-1) f[n-1][n-1]=min(f[n-1][n-1],f[i][n-1]+d(i,n-1)); if(j!=n-1) f[n-1][n-1]=min(f[n-1][n-1],f[n-1][j]+d(j,n-1)); } } printf("%.2lf",f[n-1][n-1]); return 0; //fclose(stdin); //fclose(stdout); }
t4 简单的序列
题目
【题目描述】
从前有个括号序列s,满足|s|=m。你需要统计括号序列对(p,q)的数量。
其中(p,q)满足|p|+|s|+|q|=n,且p+s+q是一个合法的括号序列。
【输入格式】
第一行两个正整数n,m。
第二行一个长度为m的括号序列,表示s。
【输出格式】
输出一行一个整数,表示符合条件的(p,q)的数量对10^9+7取模的值。
【数据规模】
对于10%的数据,n≤20;
对于25%的数据,n≤200;
对于另外5%的数据,n=m;
对于55%的数据,n-m≤200;
对于100%的数据,1≤m≤n≤10^5,n-m≤2000。
解析
这题有点意思,其实是一道dp题。
将'('转换为1,')'转换为-1。
令f[i][j]表示前i个括号总值为j(边界:f[0][0]=f[1][1]=1)。
而这里的j是一定不为负数的。为什么?因为若j为负数,就意味着当前右括号比左括号多,
这种情况即使后面加入左括号,就会变成“)(”这种情况,即右括号在左,左括号在右,很显然是不合法的。
所以初始赋值时,令f[i][0]=f[i-1][1],因为如果是f[i][0]=f[i-1][-1]的话就成了上面说的情况,不合法。
梳理了这么多,可以轻易推出状态转移方程:
1、f[i][j]=f[i-1][j-1](添加左括号)。
2、f[i][j]=f[i-1][j+1](添加右括号)。
code
#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> #include <queue> using namespace std; int read() { int num=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { num=(num<<1)+(num<<3)+ch-'0'; ch=getchar(); } return num*w; } const long long mod=1000000007; int n,m,temp,a[100100],minn=0x7f7f7f7f,ans; long long f[2010][2010]; char s; int main() { //freopen("bracket.in","r",stdin); //freopen("bracket.out","w",stdout); n=read(),m=read(); for(int i=1;i<=m;i++) { cin>>s; if(s=='(') a[++temp]=1; else a[++temp]=-1; } f[0][0]=f[1][1]=1; for(int i=2;i<=n-m;i++) { f[i][0]=f[i-1][1]; for(int j=1;j<=n-m;j++) f[i][j]=(f[i-1][j-1]+f[i-1][j+1])%mod; } temp=0; for(int i=1;i<=m;i++) { temp+=a[i]; minn=min(minn,temp); } for(int i=-minn;i<=n-m;i++) for(int j=-minn;j<=i;j++) { int l=n-m-i,b=temp+j; if(b>=0) ans=(long long)(f[l][b]*f[i][j]+ans)%mod; } cout<<ans; return 0; //fclose(stdin); //fclose(stdout); }