Codeforces Global Round 19
老天赏脸,又让我涨了一波分。。。
A. Sorting Parts
首先我们可以发现,如果序列本来就有序的话,那么无论我们选择哪个点重排最终还是有序的。考虑一个序列至少存在一个逆序对,那么我们完全可以将这个逆序对分开,这样的话,这个逆序对一定还存在,那么就是YES了。
B. MEX and Array
发现n才100,果断DP,直接设f[i]表示前i个数的答案即可,之后暴力枚举转移即可。
C. Andrew and Stones
首先考虑因为选择的中间的石头个数至少是偶数的话,那么它恰好就能分发到1和n中。倘若是奇数,那么它需要别人“施舍”个它一个石头,凑成偶数,这样的话,就可以恰好分完。考虑什么样的情况无解。2到n-1的石头中,无人可给予,且有奇数块的时候,这个时候无法完成。否则若存在石头可给予,那么整个中间的石头一定可以被送走完。注意特立n=3的情况,因为自己不能给予给自己。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int N=1e5+10;
int T,n,a[N],b[N];
int main()
{
// freopen("1.in","r",stdin);
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
if(n==3)
{
if(a[2]%2==1) puts("-1");
else printf("%lld\n",a[2]/2);
continue;
}
int cnt1=0,cnt0=0;
ll ans=0;
for(int i=2;i<n;++i)
{
if(a[i]%2==1) cnt1++;
else cnt0++;
ans+=a[i]/2;
}
if(ans==0&&cnt1) {puts("-1");continue;}
printf("%lld\n",ans+cnt1);
}
return 0;
}
D. Yet Another Minimization Problem
我们把括号拆开的话,发现无论换与不换,平方项都是不变的,变得都是2ab的乘积项,通过这个我们可以发现,我们其实只需要保留前面的和即可。直接设dp,f[i][j]表示前i项,a的和为j的最小值。
暴力转移即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int N=110,M=10010,INF=1e9;
int T,n,a[N],b[N],sum[N],f[N][M];
int main()
{
// freopen("1.in","r",stdin);
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
{
scanf("%d",&b[i]);
sum[i]=sum[i-1]+a[i]+b[i];
}
for(int i=1;i<=n;++i)
for(int j=0;j<=sum[n];++j) f[i][j]=INF;
f[1][a[1]]=0;f[1][b[1]]=0;
for(int i=2;i<=n;++i)
for(int j=0;j<=sum[i-1];++j)
{
f[i][j+a[i]]=min(f[i][j+a[i]],f[i-1][j]+2*j*a[i]+2*(sum[i-1]-j)*b[i]);
f[i][j+b[i]]=min(f[i][j+b[i]],f[i-1][j]+2*j*b[i]+2*(sum[i-1]-j)*a[i]);
}
int ans=INF;
for(int j=0;j<=sum[n];++j) ans=min(ans,f[n][j]);
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j) ans+=a[i]*a[i]+b[i]*b[i]+a[j]*a[j]+b[j]*b[j];
printf("%d\n",ans);
}
return 0;
}
E. Best Pair
结束前1分多钟交上去,没想到直接过了,始料未及....
首先我们肯定要将输入的数据进行整理,离散化,统计个数...统计每个数出现的个数即原本的值。之后考虑最大的f(u,v),由于是(cntx+cnty)⋅(x+y).的关系,我们观察到,在cnt相等的情况下,本身的权值越大越好,所以我们可以想到将所有的数按照cnt分组,之后考虑这样分组的话,组数最多是sqrt(n)的。考虑答案一定是某一组内的两个数的结果,要么是某两个不同组内的两个数的结果过。先考虑第一个,同组内的话,我们依次枚举每一个数,考虑它的另一个数,直接按照权值从大到小枚举,找到第一个合法的之后就可以直接break了,因为后面的一定没这个优。同理,两组的也这么做。考虑这样做的复杂度,由于坏的匹配只有m个,所以我们最多把这m个匹配找完就不会做无用功了。总的复杂度为nlogn。由于我太懒,当时又太紧张,离散化,没有预处理,导致我写的代码是nlog^2,不过还是过了,谢天谢地...
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
const int N=3e5+10;
int T,n,m,a[N],b[N],num,cnt[N];
struct wy{int val,cnt;}q[N];
map<int,bool>mp[N];
inline int find(int x){return lower_bound(b+1,b+num+1,x)-b;}
vector<pair<int,int> >v[N];
inline bool cmp(wy a,wy b)
{
if(a.cnt==b.cnt) return a.val>b.val;
return a.cnt<b.cnt;
}
int main()
{
// freopen("1.in","r",stdin);
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
num=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=num;++i) mp[i].clear(),cnt[i]=0,v[i].clear();
for(int i=1;i<=n;++i) cnt[find(a[i])]++;
for(int i=1;i<=num;++i) q[i].cnt=cnt[i],q[i].val=b[i];
sort(q+1,q+num+1,cmp);
for(int i=1;i<=m;++i)
{
int x,y;scanf("%d%d",&x,&y);
int t1=find(x),t2=find(y);
mp[t1][t2]=1;mp[t2][t1]=1;
}
int tot=0;
for(int i=1;i<=num;++i)
{
int j=i;
while(j+1<=num&&q[j+1].cnt==q[i].cnt) ++j;
++tot;
for(int k=i;k<=j;++k) v[tot].push_back({q[k].val,q[k].cnt});
i=j;
}
ll ans=0;
for(int i=1;i<=tot;++i)
{
for(int j=0;j<v[i].size();++j)
{
for(int k=j+1;k<v[i].size();++k)
{
pair<int,int>x=v[i][j],y=v[i][k];
if(mp[find(x.first)].find(find(y.first))!=mp[find(x.first)].end()) continue;
ans=max(ans,(ll)(x.first+y.first)*(y.second+x.second));
break;
}
}
for(int j=i+1;j<=tot;++j)
{
for(int l=0;l<v[i].size();++l)
{
for(int k=0;k<v[j].size();++k)
{
pair<int,int>x=v[i][l],y=v[j][k];
if(mp[find(x.first)].find(find(y.first))!=mp[find(x.first)].end()) continue;
ans=max(ans,(ll)(x.first+y.first)*(y.second+x.second));
break;
}
}
}
}
printf("%lld\n",ans);
}
return 0;
}
F. Towers
看到题,经过一番思索后发现这个题有几个关键的点:
1.最优答案一定是只在度数为1的点上设置塔楼。
2.整个树中权值最大的点比较关键。
首先我们可以想象出某个点x的权值最大为\(h_{max}\),那么也就是说必定会有某两个度数为1的点,x在这两个点的路径中,这两个点的\(e_i\)都为\(h_{max}\)。我们可以干脆的以权值最大的点为根。那么对于当前树中某个点i,他能够收到信号的条件,他在某两个度数为1的路径中,且这两个点的\(h_i\)都大于等于\(h_i\)。考虑到我们在最开始时已经设置了两个点的权值为\(h_max\)。那么当前点\(i\)一定能通过某种路径到达其中某个点,接下来我们只要保证它在子树内的某个叶子节点的\(e\)>=\(h_i\)即可。最初我们可以将所有度数为1的地点都设为他们初始的\(h_i\)。之后网上做dp,保留当前节点x的所有叶子节点设定的最大的\(e_i\),然后比大小,显然我们让原本中最大的\(h_i\)修改最优。至于初始确定的最大的两个节点。我们可以最后确定。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
int n,h[N],root,f[N],mx;
ll ans;
vector<int>son[N];
inline void dfs(int x,int fa)
{
int ch=0;
for(auto y:son[x])
{
if(y==fa) continue;
ch++;
dfs(y,x);
f[x]=max(f[x],f[y]);
}
if(x!=root&&h[x]>f[x])
{
ans+=h[x]-f[x];
f[x]=h[x];
}
if(x==root)
{
if(ch==1) ans+=((f[x]>=h[x])?0:(h[x]-f[x]))+h[x];
else
{
int mx1=0,mx2=0;
for(auto y:son[x])
{
if(f[y]>mx1) mx2=mx1,mx1=f[y];
else if(f[y]==mx1) mx2=mx1;
else mx2=max(mx2,f[y]);
}
ans+=((mx1>=h[x])?0:(h[x]-mx1))+((mx2>=h[x])?0:(h[x]-mx2));
}
}
}
int main()
{
// freopen("1.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&h[i]);
if(h[i]>mx) mx=h[i],root=i;
}
for(int i=1;i<n;++i)
{
int x,y;
scanf("%d%d",&x,&y);
son[x].push_back(y);
son[y].push_back(x);
}
dfs(root,0);
printf("%lld",ans);
return 0;
}