题目链接:A题 Energy Conversion
题目大意:
给出N,M,V,K四个正整数,数M可以用公式M=(M-V)*K变换,问:M最终能大于K吗?
这是一道送分题。
首先,如果一开始给出的M比N大,那么结果肯定是"YES"
那么,接下来考虑一开始M比N小的情况,可以想象,要使得最后的M比N大,那么必然要通过公式去变换,而且必须是M使用公式后的值比使用前要大。
注意数据类型要用long long 型
下面是AC代码:
#include<iostream>
#define LL long long
using namespace std;
int main()
{
LL t,n,m,v,k;
cin>>t;
while(t--)
{
cin>>n>>m>>v>>k;
LL pre=m,cnt=0;//pre是保存上一次的m
while(m<n)
{
m=(m-v)*k;//计算下一次的m
if(m<=pre) {cnt=-1;break;}//如果下一次的m不大于上一次的m,则永远也不可能比n大
cnt++,pre=m;
}
cout<<cnt<<endl;
}
return 0;
}
题目链接: B题 Disk Schedule
题目大意:
对磁盘进行读取数据,共有n个磁道,360个扇区,磁头从0磁道0扇区开始对分布在n个磁道的n个数据进行读取,每个磁道有且仅有一个数据分布在编号为0-359中的某个一扇区中。磁头每次只允许在相邻磁道中切换,而磁头每次可以在同个磁道的扇区之间可以任意切换。切换一次磁道花费800,切换一次扇区花费10,读取数据花费10,问读取所有数据最小的花费。
题目分析:如果之前知道双调欧几里德旅行商问题的模型,那么这道题并不难,如果未接触过,那一时半会儿也是想不出来的,考的是知识的广度。
本题是磁盘从第0圈至第n圈,然后又从第n圈回来,求最小花费,数据花费为n*10,磁头在磁道上转移花费n*800,这些是确定的,剩下的就是计算磁头在扇区之间的转移的花费了实则就是双调旅程问题。
双调旅途问题可以看这篇博文,写得非常好:双调旅程
下面是AC代码:
#include<iostream>
#include<cmath>
#define LL long long
using namespace std;
const int maxn=1005,inf=1<<30;
LL n,m,a[maxn],dp[maxn][maxn];
LL dis(int i,int j)//计算在两个扇区之间转化所需要的花费
{
LL x1=a[i],x2=a[j];
if(x1<x2) swap(x1,x2);
return min(x1-x2,360-(x1-x2));
}
LL solve()
{
dp[0][1]=dp[1][0]=dis(1,0);//初始化边界
for(int i=2;i<=n;i++)
{
dp[i][i-1]=inf;
for(int j=0;j<i-1;j++)
{
dp[i][j]=dis(i,i-1)+dp[i-1][j];//如果j<i-1
dp[i][i-1]=min(dp[i][i-1],dis(i,j)+dp[i-1][j]);//如果j=i-1
}
}
LL ans=inf;
for(int i=0;i<n;i++) ans=min(ans,dp[n][i]+dis(i,n));//求解dp[n][n];
return ans+m*800+n*10;
}
int main()
{
int t;
cin>>t;
a[0]=0;
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>m>>a[i];
cout<<solve()<<endl;
}
return 0;
}
题目链接: C题 Xor Sum
题目大意:
先给出n个数,然后再给出m个询问,对于每个一个询问,求这n个数中哪一个数与m的异或值最大。
n,m<=100000
显然直接枚举是不行的。
那么我们可以知道,固定一个数的值要使得两个数的异或值最大,那么可以把他们先化为二进制数。然后对于每一个相应的二进制位,都尽量使得它们是互补的。对于一个询问,我们从高位开始查询,尽量使得高位互补,如果我们能做到高效查询就好了。事实上字典树就能做到这一点。
首先,对给定的n个数,对于每个数,都先转成二进制,然后建立字典树。
对于每个询问,我们也先转成二进制,由于,询问的那个数中某二进制如果为0的话,那么需要在字典树查找1,为了方便起见,我们先把二进制中的每一位反转一下,这样,在字典树中,我们就直接找相同的就行了。
,这样我们就可以构造出a^b的结果记为c,但题目中并非叫我们求异或之后的最大值,那么要还原b,只需要让c^a就可以了,因为c^a=(a^b)^a=b;
这样的对于m个查询,每个查询只需要32次,所有复杂度为O(32*n+32*m),足以在时限内解决这个问题。
没接触过字典树的有必要先去了解一下,很简单的一种数据结构。
例如字典树可以参考一下这个博客,虽然写得不太好,字典树
下面是AC代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define LL long long
using namespace std;
struct node
{
node *next[2];
node() { memset(next,0,sizeof(next)); }
};
node *p,*root;
void Insert(char s[])//建立字典树
{
p=root;
for(int i=0;s[i];i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL) p->next[id]=new node();
p=p->next[id];
}
}
LL Query(char s[])
{
p=root;
char ans[40];//构造异或值最大的字符串
for(int i=0;s[i];i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
{
p=p->next[1-id];
ans[i]='0';
}
else
{
p=p->next[id];
ans[i]='1';
}
}
LL res=0;
// cout<<ans<<endl;
for(int i=0;i<32;i++)
res=res*2+ans[i]-'0';//转成整数
//cout<<"res = "<<res<<endl;
return res;//异或之后的最大值
}
int main()
{
int t,n,m,Case=1;
scanf("%d",&t);
char s[40];
while(t--)
{
memset(s,0,sizeof(s));
root=new node();
scanf("%d%d",&n,&m);
printf("Case #%d:\n",Case++);
for(int i=0;i<n;i++)
{
LL x;
scanf("%I64d",&x);
for(int i=31;i>=0;i--)//将数字转换成字符串表示的二进制
{
s[i]=x%2+'0';
x/=2;
}
// cout<<s<<endl;
Insert(s);//对每个字符串插入字典树中,建立字典树
}
for(int i=0;i<m;i++)
{
LL x,y;
scanf("%I64d",&x);
y=x;
for(int i=31;i>=0;i--)//先转成二进制
{
s[i]=x%2+'0';
x/=2;
}
for(int i=0;i<32;i++)//反转
if(s[i]=='0') s[i]='1';
else s[i]='0';
LL ans=Query(s)^y;//得到结果然后异或y还原那个数
printf("%I64d\n",ans);
}
}
return 0;
}
<a target=_blank href="http://acm.hdu.edu.cn/showproblem.php?pid=4826">
</a>
题目大意:给出一个矩阵,对于每个格点,可以从当前格点走到上面,下面或右边的格点,当然不可以出界,求从左上角到右上角路径上格点之和的最大值。
算法分析:
比较简单的DP题,很容易知道状态转移方程为:
dp[i][j][f]=max{dp[i][j][f],dp[i][j][k]+a[i][j]};
0<=k<3,0<=i<n,0<=j<m;
其中,k代表下一步要走的方向,f代表当前方向。
dp[i][j][f]代表从i,j点方向为f到达目标点的距离最大值
如果觉得直接递推比较难写,可以考虑,用记忆化搜索,用递归就很直观了,复杂度也是一样的。这里是用记忆化搜索实现的。
下面是AC代码:
#include<iostream>
using namespace std;
const int maxn=105,inf=1<<30;
int dp[maxn][maxn][3],a[maxn][maxn],n,m;
int dir[3][2]={{0,1},{-1,0},{1,0}};
int dfs(int x,int y,int f)
{
if(dp[x][y][f]!=inf) return dp[x][y][f];//如果已经访问过了
if(x==0&&y==m-1) return a[x][y];//到达终点
int Max=-inf;
for(int i=0;i<3;i++)//枚举三个方向
{
int nx=dir[i][0]+x,ny=dir[i][1]+y;
if((i==1&&f==2)||(i==2&&f==1)) continue;//限制不让回去,比如当前点事它上面一个点拓展过来的,它自然不可以拓展回去
if(0<=nx&&nx<n&&0<=ny&&ny<m)
Max=max(Max,a[x][y]+dfs(nx,ny,i));
}
return dp[x][y][f]=Max;
}
int main()
{
int t,Case=1;
cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=0;i<maxn;i++)
for(int j=0;j<maxn;j++)
for(int k=0;k<3;k++) dp[i][j][k]=inf;//初始化
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>a[i][j];
cout<<"Case #"<<Case++<<":"<<endl;
cout<<dfs(0,0,0)<<endl;
}
return 0;
}