第七周
作业A TT的魔法猫
给定N个人和M个胜负关系,每个胜负关系A B表示A能胜过B。胜负关系具有传递性,即A赢B,B赢C,则必有A赢C。求不能被预知胜负的选手对数。
通过分析,我们知道对于每对选手只会有三种情况:一、题目已明确给定胜负关系;二、题目未给定胜负关系但可以由其他选手的胜负关系确定;三,没有胜负关系。第一种情况无需讨论,按照题目输入即可。第二三种情况相互对立,即我们只需要一个函数便可以处理该问题:对于每一个成员,我们都将其作为可能的媒介,判断任意两名成员是否可以通过媒介判断胜负关系。可以则更新,不可则忽略。经过这种Floyd算法的变形之后,我们就能判断任意两名成员间是否存在胜负关系,从而得到不能被预知的对手数。
Floyd算法的时间复杂度是n^3,数据范围为500,有超时的风险。为避免该风险,我们可以在第二重for循环中加入判定条件,不满足直接跳出,不去进行第三次循环。经过剪枝处理,可以满足该题时间限制条件。
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
int dis[501][501];
int main(){
int count;
int M,N,A,B;
cin>>count;
while(count>0){
for(int i=1;i<=500;i++){
for(int j=1;j<=500;j++){
dis[i][j]=0;}}
cin>>N>>M;
for(int i=0;i<M;i++){
cin>>A>>B;
dis[A][B]=1;}
for(int k=1;k<=N;k++){
for(int i=1;i<=N;i++){
if(dis[i][k]!=0){
for(int j=1;j<=N;j++){
if(dis[k][j]!=0){
dis[i][j]=1;}
}}}}
int sum=0;
for(int i=1;i<=N;i++){
for(int j=1;j<i;j++){
if(dis[i][j]==0&&dis[j][i]==0){
sum++;
}
}
}
cout<<sum<<endl;
count--;
}
}
B题 TT的旅行日记
给定车站总数,出发的起点与终点,M条经济线与K条商业线。要求商业线最多坐一次,输出到达终点时间最短时经过的每个车站,换乘商业线的车站编号(未乘坐商业线输出“Ticket Not Used”)以及到达终点的总时间。
我们如果把每条线路的起点和终点作为图中一条边的两个顶点,将该线路耗费的时间作为这条边的权重。那么本题的最短时间问题就可以转化成图中的最短路径问题。利用迪杰斯特拉算法,我们如果从起点开始,便可以得到起点到图中所有点的最短路径,从而得到起点到终点的最短路径。但是这道题还有另一个问题:商业线最多只能选择一次。如何满足这个条件?我们可以将每条经济线分为两种情况:选择商业线前被选择与选择商业线后被选择。这两种经济线是互不干扰的,只有商业线才是连通这两者之间的桥梁。而如果我们将商业线规定为单向,就可以实现选择一次商业线的条件。通过这种操作,我们最后会得到两条路径:一条是经过一次商业线得到的,一次是未经过商业线得到的。比较两条路径的总权重值,较小的一条便是我们要求得到的最短路径。路径中的部分信息也即为我们所希望得到的答案。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
#define inf 1000000000
using namespace std;
struct Edge{
int v, w;
Edge(){}
Edge (int v1, int w1){
v=v1,w=w1;
}
};
int vis[2000],pre[2000],dis[2000],path[2000];
int N,S,E;
priority_queue<pair<int, int> > q;
vector<Edge> e[2000];
void dijkstra(int start)
{
while (!q.empty())
q.pop();
memset(vis,0,sizeof(vis));
for(int i=1;i<=2000;i++)
dis[i]=inf;
dis[start]=0;
pre[start]=0;
q.push(make_pair(0,start));
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u])
continue;
vis[u]=1;
for(int i=0;i<e[u].size();i++)
{
int v=e[u][i].v, w=e[u][i].w;
if(dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
pre[v]=u;
q.push(make_pair(-dis[v], v));
}}}}
int main()
{ int num=0;
while(scanf("%d%d%d",&N,&S,&E)!=EOF)
{ int M,K,u,v,w;
cin>>M;
for(int i=0;i<M;i++)
{
scanf("%d%d%d",&u,&v,&w);
Edge a(v,w);
e[u].push_back(a);
Edge b(u,w);
e[v].push_back(b);
Edge c(v+500,w);
e[u+500].push_back(c);
Edge d(u+500,w);
e[v+500].push_back(d);
}
cin>>K;
for(int i=0;i<K;i++)
{
scanf("%d%d%d",&u,&v,&w);
Edge a(v+500,w);
e[u].push_back(a);
Edge b(u+500,w);
e[v].push_back(b);
}
dijkstra(S);
if(num!=0) cout<<endl;
else num=1;
int x=0,min=0,i=0;
if(dis[E]>dis[E+500]){
min=dis[E+500];
int m=E+500;
while(pre[m]!=0){
if(m>500&&path[m]<500)
x=pre[m];
if(m>500){path[i]=m-500;}
else path[i]=m;
m=pre[m];
i++; }
if(m>500&&path[m]<500)
x=pre[m];
path[i]=m;
cout<<path[i];
i--;
while(i>=0){
cout<<" "<<path[i];
i--;
}
cout<<endl<<x<<endl;
}
else
{
min=dis[E];
int m=E;
while(pre[m]!=0){
path[i]=m;
m=pre[m];
i++; }
path[i]=m;
cout<<path[i];
i--;
while(i>=0){
cout<<" "<<path[i];
i--;
}
cout<<endl<<"Ticket Not Used"<<endl;
}
cout<<min<<endl;
for(int i=0;i<=1010;i++)
e[i].clear();
}
return 0;}
C题 TT的美梦
给定N个城市和M条道路,每个城市标记一个正整数表示其繁荣程度,沿道路从一个城市走到另一个城市,收取(目的地繁荣程度-出发地繁荣程度)^3的税,求解从首都出发走到其他城市至少要交的税钱。
将城市作为边的顶点,将税收作为边的权重,该题也是最短路径求解问题。与上一题不同的是,该题存在权值为负数的情况,图中便有可能存在负权环路。除两点之间不连通之外,负权环路的情况也标志着两点之间没有最短路。两点之间不连通的情况较为好找,那么如何判断图中有无负环呢?由常识可知,最短路径经过的路径条数必定小于图中节点数,如果不满足这一条件,则我们可以判断遇到了负环。将负环中的点都做上标记,我们便得知起点与这些点中没有最短路的存在。没有最短路的情况考虑完毕,我们就需要考虑最短路存在的情况。由于边的权值可能为负,我们采用SPFA算法求解起点到图中任意一点的最短路。最后根据要求完成相应的输出。
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<vector>
#include<queue>
#define inf 1000000000
using namespace std;
struct Edge{
int v,w;
Edge(){}
Edge(int v1,int w1){
v=v1;
w=w1;
}};
vector<Edge> e[201];
int pre[201];
int dis[201];
int inq[201];
int cnt[201];
int a[201];
queue<int> q;
void dfs(int u){
if(pre[u]==1)
return;
pre[u]=1;
inq[u]=1;
for(int i=0;i<e[u].size();i++)
dfs(e[u][i].v); }
void SPFA(int s,int N)
{
for (int i=1;i<=N;i++)
{ dis[i]=inf;
pre[i]=0;
inq[i]=0;
cnt[i]=0;}
dis[s]=0;
inq[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=0;
if(pre[u]==1)
continue;
for(int i=0;i<e[u].size();i++)
{
int v=e[u][i].v;
if (dis[v]>dis[u]+e[u][i].w)
{
cnt[v]=cnt[u]+1;
if(cnt[v]>=N){
dfs(v);
}
dis[v]=dis[u]+e[u][i].w;
if (!inq[v])
{
q.push(v);
inq[v] = 1;
}}}}}
int main(){
int T;
scanf("%d",&T);
for(int i=1;i<=T;i++){
int N;
scanf("%d",&N);
for(int j=1;j<=N;j++){
scanf("%d",&a[j]);
e[j].clear();}
int M;
scanf("%d",&M);
int u,v,value;
for(int j=0;j<M;j++){
scanf("%d%d",&u,&v);
value=pow(a[v]-a[u],3);
Edge E(v,value);
e[u].push_back(E);
}
SPFA(1,N);
int Q;
scanf("%d",&Q);
printf("Case %d:\n",i);
for(int j=0;j<Q;j++){
int P;
scanf("%d",&P);
if(pre[P]==1||dis[P]<3||dis[P]==inf)
printf("?\n");
else
printf("%d\n", dis[P]);
}}}
CSP
A题 HRZ的序列
给定一个序列,判断是否存在一个数K,使得一些数加上K,一些数减去K,一些数不变,使得整个序列里所有的数都相等。
由于K是一个固定的数字,且只能进行一步运算,那么如果给定序列中数字的种类超过了3,K必不可能存在。由此得到第一个判定条件:数字种类大于3则直接排除。如果序列中数字的种类为1种或2种,则K必存在(一种的情况下K为0,两种的情况下K为两数之差),而如果序列中数字的种类为3种,则需要另外判断:将三个数字按顺序排列,如果最大数与最小数之和为中间数的二倍,则K存在,反之则不存在。
该题的错误一方面是忽略题目数据范围,另一方面在数字种类为3时片面认为都符合情况。遇题仔细认真,多加思考,这是这道题带给我的教训。
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
long long int a[1000001];
long long int b[5];
int main(){
int t;
scanf("%d",&t);
for(int k=0;k<t;k++){
for(int i=0;i<5;i++) {b[i]=0;}
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%lld",&a[i]);}
b[0]=a[0];
int sum=1;
int flag=0;
int flag1=0;
for(int i=0;i<n;i++){
flag=0;
for(int j=0;j<sum;j++){
if(b[j]==a[i]){
flag=1;
}
}
if(flag==0) {b[sum]=a[i];sum++;}
if(sum>3) {i=n;flag1=1;}
}
if(flag1==1) {cout<<"NO"<<endl;}
else {if(sum==3){
sort(b,b+3);
if(b[2]+b[0]==2*b[1]){
cout<<"YES"<<endl;
}
else cout<<"NO"<<endl;}
else{cout<<"YES"<<endl;}}
}
}
B题 HRZ学英语
给定一个字符串,字符串中包括26个大写字母和特殊字符?。?可以表示任何一个大写字母,判断字符串中是否存在一个位置连续的且由26个大写字母组成的字串(要求每个字母出现且仅出现一次)。如果存在则输出从左侧算起第一个符合要求且字典序最小的字符串。
26个字母都只能出现一次,说明字符串长度为26。由于?可以表示任意一个字母,即当某字母不存在时可由一个?代替。所以我们每次判断?个数与不存在的字母数是否匹配即可。先处理最早的长度的字符串:如果某字母数量由一变零,证明该字母存在后又消失,不存在个数加一;如果某字母数量由零变一,说明该字母存在,不存在个数减一。处理之后比较?数量与不存在字母个数,不满足条件则右移一位重复判断。为避免超时,每次只需对头部去除的一位与尾部新加入的一位字母进行处理即可(如果两个位置上字母相等则不进行处理),直至找到第一个符合要求的字符串。在输出时?位置按照字母字典序小优先的原则完成相应字母的输出。
该题错误出现在最开始对于字母是否存在的判断上,限制条件没有实现;还有依次右移处理最初未实现算法优化导致超时。
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
char s[1000000];
int main(){
gets(s);
int sum[27];
for(int i=0;i<=26;i++){
sum[i]=0;}
int num=26;
int flag=0;
for(int i=0;i<26;i++){
if(s[i]=='?')
{sum[0]++;}
else
{if(sum[s[i]-64]==1) {num++;}
else if(sum[s[i]-64]==0){num--;}
sum[s[i]-64]++;}}
if(num==sum[0]){flag=1;}
int j=0,h=26;
while(flag==0&&h<strlen(s)){
if(s[j]!=s[h]){
if(s[j]=='?')
{sum[0]--;}
else
{if(sum[s[j]-64]==1) {num++;}
else if(sum[s[j]-64]==2){num--;}
sum[s[j]-64]--;}
if(s[h]=='?')
{sum[0]++;}
else
{if(sum[s[h]-64]==1) {num++;}
else if(sum[s[h]-64]==0){num--;}
sum[s[h]-64]++;}
if(num==sum[0]) {flag=1;}}
j++;
h++;
}
char x;
if(flag==0) {cout<<-1<<endl;}
else {int m=1;
while(j<h){
if(s[j]=='?'){
for(int k=m;k<=26;k++){
if(sum[k]==0){
x=k+64;
cout<<x;
sum[k]=1;
m=k;
break;}}}
else cout<<s[j];
j++;
}}}
C题 咕咕东的奇妙序列
对于一个无限数列1121231234123451234561234567……第一部分包括1至1之间所有数字,第二部分包括1至2之间所有数字……以此类推。给定一个数字K,输出第K项数字的值。
通过观察序列,我发现:第一到第九部分两部分之间长度差1,第十到第九十九部分两部分之间长度差2……以此类推。也就是说,我们可以将所有部分分为几个模块,每一个模块中部分长度都是一个等差数列。我们先求出K项在第几个模块,再利用该模块的求和公式便可以得到K项在第几个部分。当得到这一信息时,我们再对一个部分进行分析:数字小于10时一项表示一个数,大于10小于100时两项表示一个数……以此类推。我们就可以判断出K项所对应的数字属于哪个数字的一部分,得到这一信息后,我们再计算K项是这个数字的第几位数,层层计算,我们最后就得到了K项数字的值。
#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;
int main(){
int q;
cin>>q;
for(int i=0;i<q;i++){
long long int k;
int n=1;//级数
long long int m=1;//首项
long long int p=n*9*pow(10,(n-1));//末项
long long int q=1;
cin>>k;
long long int sum=m*9*pow(10,(n-1))+(9*pow(10,(n-1)))*(9*pow(10,(n-1))-1)*n/2;//和
while(k>sum){
k=k-sum;
q=pow(10,n);
n++;
m=p+n;
p=p+n*9*pow(10,(n-1));
sum=m*9*pow(10,(n-1))+(9*pow(10,(n-1)))*(9*pow(10,(n-1))-1)*n/2;
}
long long int num=0;
long long int x=0;
for(int j=n;j>=1;j--){
num=1;
sum=m*(num*pow(10,(j-1))+x)+(num*pow(10,(j-1))+x)*(num*pow(10,(j-1))+x-1)*n/2;
while(k>sum){
num++;
sum=m*(num*pow(10,(j-1))+x)+(num*pow(10,(j-1))+x)*(num*pow(10,(j-1))+x-1)*n/2;
}
x=x+(num-1)*pow(10,(j-1));
}
sum=m*x+x*(x-1)*n/2;
k=k-sum;
n=1;
q=1;
sum=(9*pow(10,(n-1)))*n;//和
while(k>sum){
k=k-sum;
q=pow(10,(n));
n++;
sum=(9*pow(10,(n-1)))*n;
}
k=k-1;
x=k/n;
x=x+q;
for(int i=0;i<n;i++){
if(k%n==i){
x=x/pow(10,(n-1-i));
break;
}
else {q=x/pow(10,(n-1-i));
x=x-q*pow(10,(n-1-i));}}
cout<<x<<endl;
}
}
第八周
A题 区间选点问题
给定一个数轴上的n个区间,要求在数轴上选取最少的点使得第i个区间内的点符合要求,输出最少选取的点数。
第i个区间内的点要符合要求,说明任意区间【a,b】,数轴上0到b的点数要比0
到a-1的总数至少多出条件要求的数目。我们通过最长路求解该问题,将区间两端作为边的两点,最小要求点数作为边的权重,这样我们得到的到每一点的最长路径长度即为数轴上0到该点区间点数符合要求的最小解。由此得到最小选取的点数。
为了保证选点是有意义的,我们还需要加入限定条件使得只能选取整数点。
#include<iostream>
#include<stdio.h>
#include<vector>
#include<queue>
#define inf 1000000000
using namespace std;
struct Edge{
int v,w;
Edge(){}
Edge(int v1,int w1){
v=v1;
w=w1;
}};
vector<Edge> e[50050];
int vis[50050];
int dis[50050];
queue<int> q;
void spfa(int s,int n){
for(int i=0;i<=n;i++) dis[i] = -inf, vis[i] = 0;
q.push(s);
dis[s] = 0;
vis[s] = 1;
while(!q.empty()) {
int u=q.front(); q.pop();
vis[u]=0;
for(int i=0;i<e[u].size();i++) {
int v=e[u][i].v;
if(dis[v]<dis[u]+e[u][i].w) {
dis[v]=dis[u]+e[u][i].w;
if(!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
}
int main(){
int n;
scanf("%d",&n);
int max=0;
for(int i=0;i<n;i++){
int u,v,value;
scanf("%d%d%d",&u,&v,&value);
if(v+1>max) max=v+1;
Edge E(v+1,value);
e[u].push_back(E);}
for(int j=1;j<=max;j++){
Edge E1(j,0);
e[j-1].push_back(E1);
Edge E2(j-1,-1);
e[j].push_back(E2);}
spfa(0,max);
printf("%d\n",dis[max]);}
B题 猫猫向前冲
给定N只猫咪和M场比赛结果。输出字典序最小的名次序列。
如果A猫赢了B猫,那么最后的序列中A猫一定在B猫前面,如果两猫之间没有胜负关系,那么按照题目要求,需要字典序由小到大输出。为此,我们可以采取拓扑排序的方法,将猫咪之间的胜负关系作为图中的边,将更新后入度为0的点不断压入队列。更新后入度为0则说明所有与之有依赖关系的点已从队列中删除,即队列中的点必定没有胜负关系,而应在该点之前的点已保证位置在其之前。那么我们在下一次选择时就要选择此时队列中字典序最小的点进行输出(采用优先队列实现),当队列为空时,我们就得到了最终的名次序列。
#include<iostream>
#include<stdio.h>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
struct Edge{
int v;
Edge(){}
Edge(int v1){
v=v1;
}};
vector<Edge> e[505];
int reach[505];
int p[505];
priority_queue<int> q;
int main(){
int N,M;
while(scanf("%d%d",&N,&M)!=EOF){
for(int i=1;i<=N;i++){
reach[i]=0;
e[i].clear();
p[i]=0;
}
for(int i=0;i<M;i++){
int u,v;
scanf("%d%d",&u,&v);
Edge E(v);
e[u].push_back(E);
reach[v]++;}
for(int i=1;i<=N;i++){
if(reach[i]==0){
q.push(-i);
}
}
int j=0;
while(!q.empty()){
int u=-q.top();
p[j]=u;
j++;
q.pop();
for(int i=0;i<e[u].size();i++){
int v=e[u][i].v;
reach[v]--;
if(reach[v]==0){
q.push(-v);
}
}}
for(int i=0;i<N-1;i++){
cout<<p[i]<<" ";
}
cout<<p[N-1]<<endl;
}}
C题 班长竞选
给定N名同学与M条意见(意见A B表示A认为B合适,意见具有传递性,即A认为B合适,B认为C合适,则A也认为C合适)。给出最高票数与最高票数的编号。
如果以意见A B 作为图中A,B之间的一条边的话,图中有可能会出现环路。我们先对图进行缩点,找到图中的环路并将其转换为图中一点再处理:转换的方法利用逆后序序列找到每一个部分并缩点。对于缩点后的图我们进行分析:最高票数出现的位置一定是出度为0的位置,那么我们不妨将图反过来,找到每个入度为0的点能到达的点,这些点中的人数(因为进行缩点所以人数不唯一)即为总票数的一部分。为什么是一部分呢?因为对于每个入度为0的点,点中的人数也不一定为一,如果存在环路,则对于环中任意一人,环中其他人都对其投了一票。所以实际总票数为遍历到的所有点的总人数与该点除了某一人外剩下的所有人。通过对每个入度为0的点获得的总票数进行比较,我们也就得到了最终的最高票数和获得最高票数的所有同学的编号。
注意:每一次遍历都不要忘记初始化!!!
#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;
struct Edge{
int v;
Edge(){}
Edge(int v1){
v=v1;
}};
vector<Edge> e1[5005];
vector<Edge> e2[5005];
vector<Edge> e3[5005];
int scc[5005];
int vis[5005];
int dfn[5005];
int reach[5005];
int a[5005];
int sum[5005];
int d=0,s=0;
void dfs1(int u){
vis[u]=1;
for(int i=0;i<e1[u].size();i++){
int v=e1[u][i].v;
if(vis[v]==0) dfs1(v);}
dfn[++d]=u;}
void dfs2(int u){
scc[u]=s;
for(int i=0;i<e2[u].size();i++){
int v=e2[u][i].v;
if(scc[v]==-1) {dfs2(v);}
else if(scc[v]!=scc[u]){
Edge E3(scc[v]);
e3[scc[u]].push_back(E3);
reach[scc[v]]++;
}}}
void dfs3(int u,int &sum,int *a){
sum=sum+a[u];
vis[u]=1;
for(int i=0;i<e3[u].size();i++){
int v=e3[u][i].v;
if(vis[v]==0) dfs3(v,sum,a);
}}
int main(){
int T;
scanf("%d",&T);
for(int j=1;j<=T;j++){
int N,M;
scanf("%d%d",&N,&M);
for(int i=0;i<5005;i++){
e1[i].clear();
e2[i].clear();
e3[i].clear();
scc[i]=-1;vis[i]=0;
dfn[i]=0;reach[i]=0;
}
for(int i=0;i<M;i++){
int u,v;
scanf("%d%d",&u,&v);
Edge E1(v);
e1[u].push_back(E1);
Edge E2(u);
e2[v].push_back(E2);}
d=0;s=0;
for(int i=0;i<N;i++){if(vis[i]==0) {dfs1(i);}}
for(int i=N-1;i>=0;i--){if(scc[dfn[i]]==-1) {dfs2(dfn[i]);s++;}}
for(int i=0;i<s;i++){a[i]=0;sum[i]=0;vis[i]=0;}
int max=0,sum1=0;
for(int i=0;i<N;i++){a[scc[i]]++;}
for(int i=0;i<s;i++){
sum1=0;
if(reach[i]==0){for(int k=0;k<s;k++){vis[k]=0;}
dfs3(i,sum1,a);
sum1--;
sum[i]=sum1;
if(sum[i]>max){
max=sum[i];
}}
}
printf("Case %d: ",j);
cout<<max<<endl;
int p=0;
for(int i=0;i<N;i++){
if(sum[scc[i]]==max){
if(p==0) {cout<<i;p++;}
else cout<<" "<<i;}}
cout<<endl;
}}