您的位置:首页 > 其它

【WHUST 2016 Individual Contest #1】解题报告

2016-07-25 13:18 666 查看
    第一次个人赛自我感觉还好,毕竟抱着大腿们都要完虐我的心态下,很快地把签到题都搞出来了。ACM主要是靠脑子,我这种卡壳常客居然一鼓作气前7题写出来了到第八个题才卡……但是一卡就是三个小时……还是自己的问题,队友都在刷专题搞大新闻,我就默默写点读题解题思路好了,但愿我们队组队赛时我也能有这个水平吧。(育肥君要加油哦)





A - SwapSort(CodeForces 489A)

题意:问一个数列使用小于N次交换变换成排序后的数列的一个方案。(N<=3000)

思路:上来就想类似归并和快排都是NlogN级别的交换,题目要求的是N次以内的一种交换,肯定不能直接在排序过程里下手。不管怎么样,我们可以快排很轻松地得到结果,于是问题变成了怎么从一个数列变成另一个已知数列:很显然,我们从1到N每个数都换到该在的位置(先处理1位置,如果1位置上不是答案1位置上的数,那么把现在1位置的数跟答案1位置的数进行一次交换,再去处理2位置……依此类推)因为每次让一个数归位要交换一次,所以至多交换了N次,Problem Solved.

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>

using namespace std;

int N,a[3010],b[3010];
int ans,x[3010],y[3010];
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+N+1);
ans=0;
for(int i=1;i<=N;i++)
{
if(a[i]==b[i])continue;
int j=i+1;
while(a[j]!=b[i])j++;
ans++;
x[ans]=i;
y[ans]=j;
a[j]=a[i];
a[i]=b[i];
}
printf("%d\n",ans);
for(int i=1;i<=ans;i++)
{
printf("%d %d\n",x[i]-1,y[i]-1);
}
return 0;
}


B - Unbearable Controversy of Being(CodeForces 489D)

题意:3000个点30000条边的有向图里找到如图所示的四点对的个数。(b和d无顺序之分)



思路:一开始做完了A看到这个题除了枚举并没有什么想法……就去做其它签到题了。回过头来一想,其实你不需要枚举四个点,你要找的是所有方案数,其实这个图就可以翻译为:两条从a出来可以两步到达c的路径。这样的基础上,我们枚举起点a,记录a走两步(dfs两层)到所有点的的路的条数,计算C(2)(k)就好了。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<vector>
using namespace std;

int T,N,M;
vector <int> p[3010];
int walked[3010];
long long rhombi;
void dfs(int node,int layer)
{
if(layer==3){walked[node]++;return;}
for(unsigned int i=0;i<p[node].size();i++)
dfs(p[node][i],layer+1);
}
int main()
{
rhombi=0;
scanf("%d%d",&N,&M);
int tempa,tempb;
for(int i=1;i<=M;i++)
{
scanf("%d%d",&tempa,&tempb);
p[tempa].push_back(tempb);
}
for(int i=1;i<=N;i++)
{
memset(walked,0,sizeof(walked));
dfs(i,1);
for(int j=1;j<=N;j++)
{
if(i==j)continue;
if(walked[j]>=2)
{
rhombi+=((long long)walked[j]*(walked[j]-1)/2);
}
}
}
printf("%I64d\n",rhombi);

return 0;
}


C - Hiking(CodeForces 489E)

题意:一条数轴,从0出发。有N(N<=1000)个歇脚点,按离0远近给出,旅行者要走到最后一个歇脚点去。旅行者有个给出的定值L(L<=10^5),是他每天都想走的距离,事与愿违,他走不了L米就会有不高兴值,而每天他都要走到歇脚点才能停。最终不高兴的总值为:∑(sqrt(实际距离(x[i]-x[j])与理想距离(L)差))/∑(所有停留过的歇脚点的风景值(b[i]))。如何选择方案让不高兴值最小,按顺序输出要停留的落脚点。

思路:%%%wrd.我只想到了dp,然而直接dp是不可以的,你每次A/B形式的答案上分子分母都要加值(A+x)/(B+y),这就无法保证局部最优性,也就是说走到第i个落脚点的最优解不一定比次优解好(好 是指往后走答案更优)。——该怎么办呢?比赛中就战略性放弃了……实际上这是个0/1分数dp,太久没写了都忘完了……在分子分母都在变的情况下,我们可以选择二分答案:如果我们有个答案ans,那么对式子进行变换:∑( sqrt( abs(x[i]-x[j]-L) ) - ans*b[i] )=0。如果有方案能找出更小的ans,那么等号应该变为大于号——同时,这个式子是满足局部最优性的,记录dp[i]=dp[j]+sqrt(abs(x[i]-x[j]-L))-ans*b[i],使dp[i]最小就能最终得出dp
,也为最小,如果dp
>0,那么ans不满足条件,要更大;dp
<0,试图找更小的ans,要更小……最后要求的是路径,加一个数组记录i状态是从哪个j状态转移过来的,这样回溯过去就好了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;

int N;
double L;
double x[1010],b[1010];
double dp[1010];
int pre[1010];
double ans;
void dfs(int node)
{
if(node==0)return;
dfs(pre[node]);
printf("%d ",node);
}
//double abs(double temp){return temp>0?temp:(temp)*(-1);}
//double max(double a,double b){return a<b?b:a;}
bool check(double ans)
{
dp[0]=0;
for(int i=1;i<=N;i++)
{
dp[i]=sqrt(abs(x[i]-L))-ans*b[i];
pre[i]=0;
for(int j=1;j<i;j++)
{
double temp=dp[j]+sqrt(abs(x[i]-x[j]-L))-ans*b[i];
if(temp<dp[i])
{
dp[i]=temp;
pre[i]=j;
}
}
}
//printf("%.8lf\n",ans);
if(dp[N]<0)
return true;
return false;
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%lf",&N,&L);
for(int i=1;i<=N;i++)
scanf("%lf%lf",&x[i],&b[i]);
double l=0,r=1e6;
double mid;
while(r-l>1e-8)
{
if(check(mid=(l+r)/2))
{
r=mid;
}
else
{
l=mid;
}
}
check(mid);
dfs(N);
return 0;
}


D - Special Matrices(CodeForces 489F)

题意:N*N(N<=500)的01矩阵,保证每一行每一列都是2个1,给定前M行的状态,求最多有多少种方案填充完整。

思路:又是思路不清晰没写出来的题,遗憾。限定每行每列都是两个1,其实每两列都是可以互换的,也就是说按行去考虑,当前填充到第i行时完全可以把填充了0个1的列移到一起,把填充了1个1的列移到一起,填充了2个1的列移动到一起,于是dp[i][j][k]表示填充i行有j列有1个1,有k列有0个1(N-j-k列已经有2个1)的方案数。其中i是一行一行的,可以用滚动数组压缩一维。思路难点在于想到列可以移动而且移动后还是可以记录方案数的

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;

int give[510];
int N,M;
long long MOD;
long long dp[2][501][501];
int main()
{
cin.sync_with_stdio(false);
//scanf("%d%d%I64d\n",&N,&M,&MOD);
cin>>N>>M>>MOD;
string temp;
for(int i=1;i<=M;i++)
{
cin>>temp;
for(int i=1;i<=N;i++)
{
//scanf("%c",&temp);
//cin>>temp;
//give[i]+=(temp-'0');
if(temp[i-1]=='1')
give[i]++;
}
//scanf("\n");
}
int st_one=0,st_two=0;
for(int i=1;i<=N;i++)
{
if(give[i]==1)
st_one++;
if(give[i]==2)
st_two++;
}

dp[M&1][st_one][N-st_one-st_two]=1;

int cur,pre;
for(int i=M+1;i<=N;i++)
{
cur=i&1;
pre=cur^1;
memset(dp[cur],0,sizeof(dp[cur]));
for(int j=0;j<=N;j++)
{
for(int k=0;k<=N;k++)
{
if(!dp[pre][j][k])continue;
if(j>=2)
{
dp[cur][j-2][k]+=dp[pre][j][k]*(j-1)*j/2;
dp[cur][j-2][k]%=MOD;
}
if(k>=2)
{
dp[cur][j+2][k-2]+=dp[pre][j][k]*(k-1)*k/2;
dp[cur][j+2][k-2]%=MOD;
}
if(j>=1&&k>=1)
{
dp[cur][j][k-1]+=dp[pre][j][k]*j*k;
dp[cur][j][k-1]%=MOD;
}
}
}
}
//printf("%I64d\n",dp[N&1][0][0]);
cout<<dp[N&1][0][0]<<endl;

return 0;
}


E - BerSU Ball(CodeForces 489B)

题意:二分图上只对权值差小于等于1的点连边,问最大匹配数。

思路:好裸的二分图最大匹配——等等,这题并不需要二分图——贪心地从最小的值开始匹配,如果能匹配上就匹配,如果不能匹配上就让小一点的那个走开,队列里下一个继续匹配……嗯,就这样。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<iostream>
#include<cmath>

using namespace std;

int n,m;
int a[110],b[110];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
scanf("%d",&m);
for(int j=1;j<=m;j++)
scanf("%d",&b[j]);
sort(b+1,b+m+1);

int pa=1,pb=1,pairs=0;
while(pa<=n&&pb<=m)
{
if(abs(a[pa]-b[pb])<=1)
{
pa++;
pb++;
pairs++;
continue;
}
if(a[pa]<b[pb])
pa++;
else
pb++;

}

printf("%d\n",pairs);

return 0;
}


F - Given Length and Sum of Digits...(CodeForces 489C)

题意:给定一个数字的长度,和该数字的各个位上数字总和,求满足条件的该数字最小为多少,最大为多少。

思路:就是个模拟,最小的时候从最高位开始填100...99,最大的时候从最高位开始填999...00,最初还WA了一发。。。因为特判长度为1,数字总和为0.。。。Should've pay more attention to details.

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

using namespace std;

int dig[101];
int main()
{
int m,s;
scanf("%d%d",&m,&s);
if(9*m<s){printf("-1 -1\n");return 0;}
if(m>1&&s<1){printf("-1 -1\n");return 0;}
if(m==1&&s==0){printf("0 0\n");return 0;}
int temps=1;
memset(dig,0,sizeof(0));
dig[1]=1;
int k=m;
while(temps<s)
{
if(s-temps>9)
{
dig[k]=9;
k--;
temps+=9;
}
else
{
dig[k]+=s-temps;
k--;
temps+=(s-temps);
}
}
for(int i=1;i<=m;i++)
printf("%d",dig[i]);
printf(" ");
memset(dig,0,sizeof(dig));
temps=0;
k=1;
while(temps<s)
{
if(s-temps>=9)
{
dig[k]=9;
k++;
temps+=9;
}
else
{
dig[k]=s-temps;
k++;
temps+=(s-temps);
}
}
for(int i=1;i<=m;i++)
printf("%d",dig[i]);
printf("\n");
return 0;
}


G - zxa and set(HDU5680)

题意:有个N个数的集合,它的每个非空子集的最小数为该子集的值,问你 包含偶数个数的子集总和 与 包含奇数个数的子集的总和 之差。

思路:按N个数从小到大排序后,第i个数字会在个数为j的集合中被计算C(j-1)(N-i)次,所以j为奇数时,

ans+=a[i]*C[j-1][N-i];

j为偶数时
ans-=a[i]*C[j-1][N-i];

嗯,就酱紫。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>

using namespace std;

int T;
int N;
long long a[40];
long long C[40][40];
long long ans;
int main()
{
C[0][0]=1;
C[0][1]=1;
C[1][1]=1;
for(int i=2;i<=30;i++)
{
C[0][i]=1;
for(int j=1;j<i;j++)
{
C[j][i]=C[j-1][i-1]+C[j][i-1];
}
C[i][i]=1;
}
scanf("%d",&T);
while(T--)
{
ans=0;

scanf("%d",&N);
for(int i=1;i<=N;i++)
scanf("%d",&a[i]);
sort(a+1,a+N+1);
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N-i+1;j++)
{
if(j%2==0)
ans-=a[i]*C[j-1][N-i];
else
ans+=a[i]*C[j-1][N-i];
//printf("%d %d %I64d\n",i,j,a[i]*C[j-1][N-i]);
}
}

printf("%I64d\n",abs(ans));
}
return 0;
}


H - zxa and wifi(HDU5681)

待补。dp。增加冗余项使dp复杂度降低。

I - ztr loves lucky numbers(HDU5676)

题意:找到最小的比给定数字大的由相同个数个4和7组成的数字。

思路:有10^5组数据而且每次给的N都是10^18以内,所以只能拿位数一位一位模拟。首先判断是不是奇数位,如果是偶数位,挨位填4还是7。要注意的细节是:

1.记录一个overwhelm代表是否前面的位已经比给定值大了,如果overwhelm==1那么有4取4,没4取7。

2.不止要记录4的个数,也要记录7的个数。当你填7填的不够了,要退回去把最近的一个4换成7,再从这个4换成7的位重新往后填。

然而,这题根本不需要这么麻烦的模拟——%%%gss。把10^18以内的所有47“幸运数”记下来就够了,每次直接在set里lowerbound就好了。orz

给出自己又长又丑的模拟代码。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>

using namespace std;

int ans[20];
int main()
{
cin.sync_with_stdio(false);
int T;
cin>>T;
string str;
while(T--)
{
cin>>str;
int len=str.length();
if(len%2==1)
{
len++;
for(int i=1;i<=len/2;i++)
printf("4");
for(int i=1;i<=len/2;i++)
printf("7");
printf("\n");
continue;
}
else
{
for(int i=1;i<=len/2;i++)
ans[i]=7;
for(int i=len/2+1;i<=len;i++)
ans[i]=4;
for(int i=1;i<=len;i++)
{
if(str[i-1]-'0'>ans[i])
{
len+=2;
for(int i=1;i<=len/2;i++)
printf("4");
for(int i=1;i<=len/2;i++)
printf("7");
printf("\n");
goto NEXT;
}
if(str[i-1]-'0'<ans[i])
break;
}
int fours=len/2,sevens=len/2;
memset(ans,0,sizeof(ans));
bool overwhelm=false;
for(int k=1;k<=len;k++)
{
if(overwhelm)
{
if(fours)
{
ans[k]=4;
fours--;
}
else
{
ans[k]=7;
sevens--;
}
}
else
{
int temp=str[k-1]-'0';
if(temp==4)
{
if(fours)
{
ans[k]=4;
fours--;
continue;
}
else
{
ans[k]=7;
sevens--;
overwhelm=1;
continue;
}
}
if(temp==7)
{
if(sevens)
{
ans[k]=7;
sevens--;
continue;
}
else
{
int kk=k-1;
while(ans[kk]==7)kk--;
ans[kk]=7;
for(int i=kk+1;i<k;i++)
if(ans[i]==4)
fours++;
else
sevens++;
fours++;
sevens--;
overwhelm=1;
k=kk;
continue;
}
}
if(temp<4)
{
if(fours)
{
overwhelm=1;
ans[k]=4;
fours--;
continue;
}
else
{
overwhelm=1;
ans[k]=7;
sevens--;
continue;
}
}
if(temp>4&&temp<7)
{
overwhelm=1;
ans[k]=7;
sevens--;
continue;
}
if(temp>7)
{
b002

int kk=k-1;
while(ans[kk]==7)kk--;
ans[kk]=7;
for(int i=kk+1;i<k;i++)
if(ans[i]==4)
fours++;
else
sevens++;
fours++;
sevens--;
overwhelm=1;
k=kk;
continue;
}

}
}
for(int i=1;i<=len;i++)
printf("%d",ans[i]);
printf("\n");
}
NEXT:;
}

return 0;
}


J - KK's Steel(HDU5620)

题意:有N米长的钢筋,想切地越多越好,但是切出来的钢筋不能长度相同,而且任意三个钢筋不能组成三角形。

思路:不能组成三角形这个太严格了,三角形最长边要小于等于另两条边,也就是说如果当前集合里最大的是a1,a2,只有a3>=a1+a2才能添加到集合中去。于是逆向思维,最少有1,有2,因为钢筋个数尽可能多就添加最小的1+2=3进去,再添加2+3=5,3+5=8...斐波那契数列了。。。找到小于前n项和小于N的最大n就好了。

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;

long long N;
int tot;
long double steel[1000000];
int main()
{
steel[1]=1;
steel[2]=2;
steel[3]=3;
tot=3;
while(tot<=1000000&&steel[tot]+steel[tot-1]<=1e18)steel[++tot]=steel[tot-1]+steel[tot-2];
//long long temp=steel[tot];
//cout<<temp<<endl;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%I64d",&N);
int temp=0;
long double m=0;
long double n=N;
while(n>=m)
{
temp++;
m+=steel[temp];
}
cout<<temp-1<<endl;
}

return 0;
}


K - KK's Number(HDU5623)

题意:二人博弈。有N<=5*10^4个数字,轮流操作,每次操作可以取走任意个数,那么这次操作的得分为这些取走的数字中最小数字的值。问两人分数差最大为多少。

思路:得分是每次操作去给的,我当时居然没有反应过来……一看就是脑子太久不用迟钝了。GG

每次取都是从大的去取,而且一定不会留下相同的数字,于是预处理一下合并相同数字。

ans[i]代表取到第i个数字,两人差最大为多少。

有ans[i]=max(ans[i-1],a[i]-ans[i-1]);//ans[i]==ans[i-1]时是把第i个数跟之前的一起取掉,ans[i]==a[i]-ans[i-1]是只取当前数字,那么相当于在i-1时是敌手的先手。

嗯-m-,这样。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;

int T;
int N,n;
long long num[50010];
long long a[50010];
long long ans[50010];
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
scanf("%I64d",&num[i]);
sort(num+1,num+N+1);
n=1;
a[1]=num[1];
for(int i=2;i<=N;i++)
if(num[i-1]!=num[i])
a[++n]=num[i];
ans[0]=0;
for(int i=1;i<=n;i++)
{
ans[i]=max(ans[i-1],a[i]-ans[i-1]);
}
//for(int i=1;i<=n;i++)
//	printf("%I64d ",ans[i]);
printf("%I64d\n",ans
);
}

return 0;
}


L - Jam's store(HDU 5619)

题意:M个员工,N个顾客,排队修理东西,问最小总等待时间。

思路:写过类似的,可惜忘记了具体思路。OI老龄痴呆选手TAT……M和N都只有20,很明显不是状压就是网络流(或者奇怪的暴力)。

正解是最小费用最大流,建边时要注意到一个性质,在同一个员工的等待队伍里有k个人,第i个人要被等待k-i次(所有i后面的人都在等),于是倒过来,让最后一个顾客作为第一个顾客考虑,倒数第二个顾客作为第二个顾客考虑……这样的好处是,建边时,第i个在等待的人的边权是i*等待时间,这样400+个点跑最小费用最大流就搞定了。

(补题时WA了一发,因为模板是没有多组数据的,在改的时候清理没有清理干净p[0]数组……)XD

#include<fstream>
#include<cstring>
#include<vector>
#include<deque>
#include<cmath>
#include<iostream>
using namespace std;
const int SIZEN=501,INF=0x7FFFFFFF;
class EDGE
{
public:
int u,v,c,w;
};
int cost,S,T;
int M,N;
int matrix[21][21];
vector <EDGE> edge;//所有边
vector <int> p[SIZEN];//点对应的边的编号
void CLEAR()
{
edge.clear();
for(int i=0;i<=N+N*M+1;i++)p[i].clear();
}
void Addedge(int u,int v,int c,int w)
{
edge.push_back((EDGE){u,v,c,w});
edge.push_back((EDGE){v,u,0,-w});
int tmp=edge.size()-2;
p[u].push_back(tmp);
p[v].push_back(tmp+1);
}
bool SPFA()
{
deque <int> q;
//spfa
bool boo[SIZEN]={0};
int i,j,d[SIZEN],father[SIZEN]={0};
for(i=0;i<=N+M*N+1;i++)d[i]=INF;
q.push_back(S);
d[S]=0;
boo[S]=1;
EDGE ed;
while(!q.empty())
{
j=q.front();
boo[j]=false;
q.pop_front();
for(i=0;i<p[j].size();i++)
{
ed=edge[p[j][i]];
if(ed.c)
if(d[ed.v]>d[j]+ed.w)
{
d[ed.v]=d[j]+ed.w;
father[ed.v]=p[j][i];//father[]记录所来的边
if(!boo[ed.v])
{
boo[ed.v]=1;
q.push_back(ed.v);
}
}
}
}
if(d[T]==INF)return false;//没有找到最短路

//找到一条路径后
//1.找流量
i=T;
int flow=INF;
while(i!=S)
{
flow=min(edge[father[i]].c,flow);
i=edge[father[i]].u;
}
//fo<<flow<<endl;
cost+=flow*d[T];
//2.减流量
i=T;
int now;
while(i!=S)
{
now=father[i];
edge[now].c-=flow;
edge[now^1].c+=flow;
i=edge[now].u;
}
return true;
}
int main()
{
ios::sync_with_stdio(false);
int Tests;
cin>>Tests;
while(Tests--)
{
cin>>M>>N;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
cin>>matrix[i][j];
}
CLEAR();
S=0;
T=N+M*N+1;
for(int i=1;i<=N;i++)
Addedge(S,i,1,0);
for(int i=1;i<=M;i++)
{
for(int j=1;j<=N;j++)
{
for(int k=1;k<=N;k++)
{
Addedge(k,N+(i-1)*N+j,1,j*matrix[k][i]);//K号顾客在i号员工处作为倒数第j个来repair的
}
Addedge(N+(i-1)*N+j,T,1,0);
}

}
cost=0;
while(SPFA());
cout<<cost<<endl;
}
return 0;
}


这大概就是第一次吧(???)。题读起来还是觉得不舒服……CF跟HDU的英文题一看就能区分出来哪个是中国人出的题……不能怪出题人,要怪就怪自己思路跟出题人不一样,说明我还不是一个够格的ACMer。那。。。加油。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ACM codeforces WHUST