题解
讲道理,真的很谴责这种挂着羊头卖着恐龙肉的操作。标题写着普及训练难度结果是NOI/CTSC的题orz
真的爆零,我一道题都不会写orz,出题人是江苏高考415的dalao…
第一题——高考题(gaokao)
【题目描述】
- 给出二元组序列 [ a i , b i ] [a_i,b_i] [ai,bi]。
- 给出方程: T i = { a i + b i i = 1 m a x ( T i − 1 , ∑ j = 1 i a i ) + b i i ≠ 1 T_i=\begin{cases} a_i+b_i &i=1\\ max(T_{i-1},\sum_{j=1}^{i}{a_i})+b_i &i\ne 1\\ \end{cases} Ti={ai+bimax(Ti−1,∑j=1iai)+bii=1i̸=1
- 现在要求重新对二元组序列排序,使得序列T当中的最大的值最小。
-
这个真的是高考题…orz,那我高考的时候怕是要gg了…
-
其实你可以手动推一下:
T 1 = a 1 + b 1 T_1=a_1+b_1 T1=a1+b1
T 2 = m a x ( T 1 , a 1 + a 2 ) + b 2 = m a x ( a 1 + b 1 + b 2 , a 1 + a 2 + b 2 ) T_2=max(T_1,a_1+a_2)+b_2=max(a_1+b_1+b_2,a_1+a_2+b_2) T2=max(T1,a1+a2)+b2=max(a1+b1+b2,a1+a2+b2)
T 3 = m a x ( T 2 , a 1 + a 2 + a 3 ) + b 3 = m a x ( a 1 + b 1 + b 2 + b 3 , a 1 + a 2 + b 2 + b 3 , a 1 + b 1 + b 2 + b 3 ) T_3=max(T_2,a_1+a_2+a_3)+b_3=max(a_1+b_1+b_2+b_3,a_1+a_2+b_2+b_3,a_1+b_1+b_2+b_3) T3=max(T2,a1+a2+a3)+b3=max(a1+b1+b2+b3,a1+a2+b2+b3,a1+b1+b2+b3)
-
总结规律你会发现其实 T i = m a x ( ∑ k = 1 j a i + ∑ k = j i b i ) T_i=max(\sum_{k=1}^{j}a_i+\sum_{k=j}^{i}b_i) Ti=max(∑k=1jai+∑k=jibi)
-
然后你会发现这个并没有什么用…
-
只能告诉你按照 m i n ( a i , b j ) < m i n ( a j , b i ) min(a_i,b_j)<min(a_j,b_i) min(ai,bj)<min(aj,bi)来排…
-
但这个还是错解…但数据是错误解造出来的orz
-
相似题目(其实是一样的)可以看这个洛谷2123,lzj大佬写的是正解…
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
inline void fff(){
freopen("gaokao.in","r",stdin);
freopen("gaokao.out","w",stdout);
}
const int N=100010;
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
int n;
struct node{
LL t1,t2;
LL d;
bool operator < (const node x)const{
// if(d!=x.d) return d<x.d;
// if(d<=0) return t1<x.t1;
// return t2>x.t2;
return min(t1,x.t2)<min(t2,x.t1);
}
}a[N];
LL c[N];
int main(){
// fff();
int T;
cin>>T;
while(T--){
memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
n=read();
for(int i=1;i<=n;i++){
a[i].t1=(LL)read();
a[i].t2=(LL)read();
if(a[i].t1>a[i].t2) a[i].d=1;
else if(a[i].t1<a[i].t2) a[i].d=-1;
else a[i].d=0;
}
sort(a+1,a+n+1);
LL sum=0;
for(int i=1;i<=n;i++){
sum+=a[i].t1;
c[i]=max(c[i-1],sum)+a[i].t2;
}
printf("%lld\n",c[n]);
}
}
第二题——高数题(gaoshu)
【题目描述】
- 在点权树中,边上存在颜色,规定所走路径不能前后经过相邻的颜色,求树上合法路径权值和。
- 唯一一个还能够想想做法的题。和上一道和下一道的玄之又玄的做法完全不一样。
- 三十分做法:暴力枚举开始节点 i i i,然后对于每一个节点 O ( n ) O(n) O(n)模拟,最终结果由于出发节点和终点是等价的,所以结果除以2。最终复杂度 O ( n 2 ) O(n^2) O(n2)
- 满分做法:
- 假设根节点root,则从根节点出发向下进行路径操作。对于每一颗子树来说,他子树下的节点的权值经过和是固定的。那么维护下从dang下面节点延伸到当前节点的权值和与he合法途径的条数。
- 显然:
- 当前节点为根节点的所有合法路径的权值和 = 之前深搜的所有子节点向上返回的边数之和
∗
*
∗当前子节点返回的分数+
之前深搜的所有子节点向上返回的分数之和 ∗ * ∗当前子节点返回的边数+之前深搜的所有子节点向上返回的边数之和 ∗ * ∗当前子节点返回的边数 ∗ * ∗ 当前点的权。
- 当前节点为根节点的所有合法路径的权值和 = 之前深搜的所有子节点向上返回的边数之和
∗
*
∗当前子节点返回的分数+
- 由于要考虑颜色,则需要将当前子树的相同路径的子树进行“合并操作”。
- 然后就可以求出解了orz。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
using namespace std;
typedef long long LL;
const int N=300010;
inline void fff(){
freopen("gaoshu.in","r",stdin);
freopen("gaoshu.out","w",stdout);
}
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
int n;
int a[N],d[N];
struct Edge{
int from,to,color;
};
vector<Edge> edge;
vector<int> G[N];
map<int,pair<LL,LL>> tt[N];
bool visited[N];
bool cmp1(int i,int j){
return edge[i].color<edge[j].color;
}
LL num[N],val[N];
LL ans;
void dfs(int u,int fuck){
int siz=G[u].size();
visited[u]=true;
sort(G[u].begin(),G[u].end(),cmp1);
for(int i=0;i<siz;i++){
Edge &e=edge[G[u][i]];
if(visited[e.to]) continue;
dfs(e.to,e.color);
ans+=(num[u]-tt[u][e.color].first)*val[e.to]+(val[u]-tt[u][e.color].second)*num[e.to]+(num[u]-tt[u][e.color].first)*num[e.to]*a[u];
tt[u][e.color].first+=num[e.to];
tt[u][e.color].second+=val[e.to];
num[u]+=num[e.to];
val[u]+=val[e.to];
ans+=val[e.to]+num[e.to]*a[u];
}
num[u]-=tt[u][fuck].first;
val[u]-=tt[u][fuck].second;
num[u]++;
val[u]+=a[u]*num[u];
}
int main(){
// fff();
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++){
int u,v,w;
u=read(),v=read(),w=read();
edge.push_back((Edge){u,v,w});
G[u].push_back(edge.size()-1);
edge.push_back((Edge){v,u,w});
G[v].push_back(edge.size()-1);
}
ans=0;
dfs(1,-1);
cout<<ans;
}
第三题——高中题(gaozhong)
【题目描述】
- 给出n个节点,m条带权边,前n-1条是好边,要求调整边的权值使得图的最小生成树只由好边构成。求调整权值代价和最小是多少(是指每对边权修改1代价就是1)
- 很明显,这个题我不会。
- 可以看得出,如果要让好边都是最小生成树当中的边,根据kruskal算法,好边的权值只能够减,坏边的权值只能够加。那么令任意一组好、坏边 i , j i,j i,j,修改前的权值是 w i , w j w_i,w_j wi,wj,调整值是 d i , d j d_i,d_j di,dj,那么可以得出 w i − d i ≤ w j + d j w_i-d_i\leq w_j+d_j wi−di≤wj+dj,再往后推一步,就可以得到 w i − w j ≤ d i + d j w_i-w_j\leq d_i+d_j wi−wj≤di+dj,而我们的目的就是求出满足这些不等式组的非负最小解(最小是指和最小)。
- 那我们就可以将他们转化成二分图的模型,好边属于同一个集合,坏边属于一个集合,要求好边与坏边之间的匹配值满足不等式。
- 然后就是KM求最大完美匹配了orz。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=550;
inline void fff(){
freopen("gaozhong.in","r",stdin);
freopen("gaozhong.out","w",stdout);
}
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
const int INF=10000000;
int n,m,nx,ny;
int s[N],e[N],d[N],belong[N],ss[N];
int f[N][N],z[N][N],lx[N],ly[N];
bool visx[N],visy[N];
bool dfs(int u,int pre,int id){
if(u==e[id]) return true;
for(int i=1;i<=n;i++){
if(i!=pre&&f[u][i]&&dfs(i,u,id)){
z[f[u][i]][id]=d[f[u][i]]-d[id];
return true;
}
}
return false;
}
bool dfs(int x){
visx[x]=true;
for(int i=1;i<=ny;i++){
if(!visy[i]){
int relax=lx[x]+ly[i]-z[x][i];
if(!relax){
visy[i]=true;
if(!belong[i]||dfs(belong[i])){
belong[i]=x;
return true;
}
}else ss[i]=min(ss[i],relax);
}
}
return false;
}
void KM(){
for(int i=1;i<=nx;i++) lx[i]=-INF;
for(int i=1;i<=nx;i++)
for(int j=1;j<=ny;j++)
lx[i]=max(lx[i],z[i][j]);
for(int i=1;i<=nx;i++){
memset(ss,0x3f,sizeof(ss));
while(true){
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(dfs(i)) break;
int delta=INF;
for(int j=1;j<=ny;j++)
if(!visy[j]) delta=min(delta,ss[j]);
if(delta==INF) return;
for(int j=1;j<=nx;j++)
if(visx[j]) lx[j]-=delta;
for(int j=1;j<=ny;j++)
if(visy[j]) ly[j]+=delta;
else ss[j]-=delta;
}
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
s[i]=read();
e[i]=read();
d[i]=read();
}
memset(f,0,sizeof(f));
memset(z,0,sizeof(z));
for(int i=1;i<n;i++) f[s[i]][e[i]]=f[e[i]][s[i]]=i;
for(int i=n;i<=m;i++) dfs(s[i],0,i);
nx=ny=m;
KM();
int ans=0;
for(int i=1;i<n;i++)ans+=lx[i];
for(int i=n;i<=m;i++)ans+=ly[i];
printf("%d",ans);
}