A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5680
官方题解:
通过观察样例猜测答案即max(ai),可以对小规模的数据进行模拟验证,实际答案就是max(ai),时间复杂度O(n)。
由于ai互不相同,因此可以考虑每个元素在哪些子集中是最小值。
设ai是A中第k大的元素,以ai为最小值的集合只能包含不小于ai的元素,因此这样的集合有2k−1个。
如果k=1,则只有{ai}以ai为最小值;否则,上述集合中包含最大元素的子集和不包含最大元素的子集的元素个数奇偶性不同,对答案的贡献相互抵消。因此有Sodd−Seven=max(ai)。
实际上,ai不需要互不相同,也可以得到答案是max(ai)的结论。
我的思考:之前想的没有观察答案,想的实在是很复杂,用了组合数,傻傻的去算,还一直在担心极端情况会不会爆ll,但也算是a了,学了下组合数递推的写法
#include<cstdio>//官方题解
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
int T,a,n;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int ans=0;
while(n--)
{
scanf("%d",&a);
ans=max(ans,a);
}
printf("%d\n",ans);
}
return 0;
}
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll __int64
using namespace std;
ll a[40],c[31][31];
void init()
{
c[0][0]=1;
for(int i=1;i<=30;i++)
{
c[i][0]=1;
for(int j=0;j<=i;j++)
c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
int main()
{//freopen("C:\\Users\\Administrator\\Desktop\\input.txt","r",stdin);
int T,n;
init();
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
ll ans=0;
for(int i=0;i<n;i++)
scanf("%I64d",&a[i]);
sort(a,a+n);
for(int i=0;i<n;i++)
{
int tmp=n-i-1;
int tmp1=2,tmp2=1;
while(tmp>=tmp1)
{
ans+=c[tmp][tmp1]*a[i];
ans-=c[tmp][tmp2]*a[i];
tmp1+=2;
tmp2+=2;
}
if(tmp>=tmp2)ans-=c[tmp][tmp2]*a[i];
}
for(int i=0;i<n;i++)
ans+=a[i];
printf("%I64d\n",ans);
}
return 0;
}
B题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5681
官方题解:
通过二分在O(nlogn)的时间复杂度下将路由器能覆盖的区间[li,ri]求出来,并令f[i][j]表示用i个路由器覆盖前j个点的最小代价。
对于第一种方案,选取j点设置路由器可以得到的转移是f[i][rj]←minlj−1≤k≤rjf[i−1][k]+ai。
对于第二种方案,选取j点设置光缆可以得到的转移是f[i][j]←f[i][j−1]+bi。
因此可以利用树状数组维护后缀区间最大值做到离线O(nklogn)。
注意到选择互相包含的区间是不会成为最优解的,因此可以将第一组转移改为f[i][rj]←minlj−1≤kf[i−1][k]+ai,即可做到O(nk)。
我的思考:比赛的时候写了下网络流,写完了发现没看见k个路由器的限制,正解是dp,类似邻接表的写法维护左右区间,学到了
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20005;
int T,n,m,i,j,k,a[N],b[N],r[N],d[N],f[105][N],h[N],ne[N],p[N];
int main()
{
scanf("%d",&T);
while(T--)
{
memset(h,0,sizeof(h));
scanf("%d%d",&n,&m);
for(i=2;i<=n;i++)
{
scanf("%d",&d[i]);
d[i]+=d[i-1];
}
d[n+1]=0x7fffffff;//处理后d[i]表示i节点和1节点的距离
for(i=1;i<=n;i++)
{
scanf("%d%d%d",a+i,r+i,b+i);
j=lower_bound(d+1,d+n+1,d[i]-r[i])-d;//左端点
k=upper_bound(d+1,d+n+1,d[i]+r[i])-d-1;//右端点
p[i]=j;//设置i的左端点为j
ne[i]=h[k];//ne[i]记录的是前一个以k为右端点的节点id
h[k]=i;//设置最近一个以k为右端点的节点id为i
}//和上一条语句一起记录了所有以k为右端点的节点集
for(i=1;i<=n;i++) f[0][i]=f[0][i-1]+b[i];//0个路由器的情况f[n][m]记录有n个路由器前m个节点网络连通的最小cost
for(int i=1;i<=m;i++)//枚举路由器个数
{
for(j=1;j<=n;j++)//枚举网络连通的节点个数
for(k=h[j],f[i][j]=f[i][j-1]+b[j];k;k=ne[k])//枚举所有以j为右端点的节点更新f[i][j]
f[i][j]=min(f[i][j],f[i-1][p[k]-1]+a[k]);
for(j=n-1;j;j--) f[i][j]=min(f[i][j],f[i][j+1]);//不以j为右端点的同样为i个路由器中可能会有更小的cost值包含了超过j个节点
}
printf("%d\n",f[m][n]);
}
return 0;
}
C题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5682
官方题解:
二分答案L,检查是否存在答案不超过L的解,也即对于任意一条边上的两个点u和v,有∣levelu−levelv∣≤L,如果能维护出每个点可能的取值区间就可以判断是否有解了。
对于n>1的情况,随便找点做根,做树形dp即可,总的时间复杂度为O(nlogmax(w))。
觉得dfs容易爆栈的话可以设一个虚点做起点,连向已经确定level的叶子,通过bfs确定一个拓扑序,在序上更新即可,时间复杂度同上。
Claris说随便选个点bfs就可以了。
这个题可以扩展,确定level的点可以不是叶子,扩展版本用虚树做也是十分容易的。
我的思考:二分答案+树形dp#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=50005;
int T,n,k,root,flag;
bool vis[N];
int id[N],w[N],head[N],tp;
struct Edge
{
int v,next;
}edge[N<<1];
void ae(int u,int v)
{
edge[tp].v=v;
edge[tp].next=head[u];
head[u]=tp++;
}
pair<int,int> dfs(int u,int f,int mid)
{
int l=0,r=1e9,v;
if(vis[u]) return make_pair(max(1,w[u]-mid),min(1000000000,w[u]+mid));
for(int i=head[u];i!=-1&&flag;i=edge[i].next)//遍历u的所有边
{
if((v=edge[i].v)!=f)//如果v不是u的前一个点防止重复遍历
{
pair<int,int> tmp=dfs(v,u,mid);//求v的区间范围
if(tmp.first>r||tmp.second<l) {flag=0;break;}//如果范围不在已知范围内跳出
l=max(l,tmp.first);
r=min(r,tmp.second);
}
}
return make_pair(max(1,l-mid),min(1000000000,r+mid));
}
bool check(int x)
{
flag=1;
dfs(root,0,x);
return flag!=0;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
memset(head,-1,sizeof(head));tp=0;
for(int u,v,i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
ae(u,v);ae(v,u);
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=k;i++)
{
scanf("%d",&id[i]);
scanf("%d",&w[id[i]]);
vis[id[i]]=1;
}
if(n==2)
{
if(k==2) printf("%d\n",max(w[1]-w[2],w[2]-w[1]));
else printf("%d\n",0);
continue;
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
root=i;
break;
}
}
int l=0,r=1000000000,mid,ans=0;//二分答案
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",ans);
}
return 0;
}
D题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5683
官方题解:
由于异或运算的特性,答案可以按位考虑,只需要维护出当前状态有多少对(ai+aj)的第k位是1,做O(logmax(ai))次即可。
(ai+aj)的第k位是1,即((ai+aj)mod2k+1)≥2k,而0≤(aimod2k+1)+(ajmod2k+1)<2k+2,所以若令bi=(aimod2k+1),则对于给定的i,满足条件的j有2k−bi≤bj<2k+1−bi或者bj≥2k+1+2k−bi。
利用离散型权值线段树可以很简便地维护题目中的修改和查询,总时间复杂度O(nlognlogmax(ai))。
我的思考:只能说暴力出奇迹,纯暴力A
#include<cstdio>
#include<cstring>
using namespace std;
const int N=20005;
int a[N];
int main()
{
int T,n,m,x,y,sum;
scanf("%d",&T);
while(T--)
{
sum=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
sum^=(a[i]+a[j]);
}
}
while(m--)
{
scanf("%d%d",&x,&y);
for(int i=1;i<=n;i++)
{
if(i!=x)
{
sum^=(a[i]+a[x]);
sum^=(a[i]+y);
}
}
printf("%d\n",sum);
a[x]=y;
}
}
return 0;
}