A. 气球
设 (f_i) 表示最后一次选择的是 (i) 的答案,暴力转移是枚举一个 (j),判断 (c_i,c_j) 是否相同对应乘上系数转移就行了。
我们记 (mx_i) 表示当前考虑完的所有 (f) 的时候 (c_k = i) 的 (f_k) 的最大值,那么我们转移只需要知道 (mx_{c_i}) 和剩下的数的最大值就行了,分别记录最大值和次大值是什么即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 1e5 + 5;
int n,q,v[MAXN],c[MAXN];
LL val[MAXN],mx,cmx;
signed main(){
scanf("%lld%lld",&n,&q);
FOR(i,1,n) scanf("%lld",v+i);
FOR(i,1,n) scanf("%lld",c+i);
while(q--){
LL a,b;scanf("%lld%lld",&a,&b);
FOR(i,0,n) val[i] = -1e18;
mx = 0;cmx = 1;
LL ans = 0;
FOR(i,1,n){
LL f = std::max(b*v[i],val[c[i]]+a*v[i]);
f = std::max(f,(mx==c[i]?val[cmx]:val[mx])+b*v[i]);
ans = std::max(ans,f);
val[c[i]] = std::max(val[c[i]],f);
if(mx == c[i]) continue;
if(val[c[i]] > val[mx]) cmx = mx,mx = c[i];
else if(val[c[i]] > val[cmx]) cmx = c[i];
}
printf("%lld
",ans);
}
return 0;
}
B. 游戏
可以证明:一定是取了若干个整行+一个散行。
反证:假设两行的元素分别是 (a,b),设分别取到了 (i,j),那么我们只需要证明:
整理可以得到:
而由于 (a,b) 是从大到小排序的,所以有 (a_{i+1}-a_i leq 0,b_{j+1}-b_{j} leq 0),得证。
但是注意直接去取和最小的整行是不对的,因为可能存在和很小但是前面很大的可能,比如
1e9 1e9 1e9
2e9 1e9 1
取 (4) 个数,如果直接贪心会全取第一行并且取第二行第一个,是 (5 imes 10^9),然而实际上应该是全取第二行再取第一行第一个,是 (4 imes 10^9 + 1)。其实搞两个和相等的,但是一个很均匀一个不均匀的就能证萎。。
所以我们去枚举没有取满的行是哪个,然后排个序就好了。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1000+5;
int n,m,k;
int a[MAXN][MAXN];
LL sm[MAXN];
int main(){
scanf("%d%d%d",&n,&m,&k);
FOR(i,1,n) FOR(j,1,m) scanf("%d",&a[i][j]),sm[i] += a[i][j];
if(k == n*m){
LL sm = 0;
FOR(i,1,n) FOR(j,1,m) sm += a[i][j];
printf("%lld
",sm);
return 0;
}
FOR(i,1,n) std::sort(a[i]+1,a[i]+m+1),std::reverse(a[i]+1,a[i]+m+1);
LL ans = 1e18;
FOR(i,1,n){
LL gx = 0;
FOR(j,1,k%m) gx += a[i][j];
std::vector<LL> S;
FOR(j,1,n) if(j != i) S.pb(sm[j]);
std::sort(all(S));
FOR(i,0,k/m-1) gx += S[i];
ans = std::min(ans,gx);
}
printf("%lld
",ans);
return 0;
}
// 选择最大值最小的一行 删除最大值
C. 树
我们可以补集转化计算相交的方案数,两个链有交当且仅当某一个链的 ( ext{lca}) 在另一个链上。枚举在哪个链上,相当于要处理一下以某个点为 ( ext{lca}),长度为 (p/q) 的链的个数和经过某个点,长度为 (p/q) 的链的个数。但是注意这样会算重两条链的 ( ext{lca}) 互相包含的情况,不难发现这种情况一定是两条链 ( ext{lca}) 相同的情况,也可以类似算出来。
需要卡常:能预处理的就不要每次都算。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 3000+5;
int n,p,q;
struct Edge{
int to,nxt;
}e[MAXN<<1];
int head[MAXN],_cnt;
inline void add(int u,int v){
e[++_cnt] = (Edge){v,head[u]};head[u] = _cnt;
e[++_cnt] = (Edge){u,head[v]};head[v] = _cnt;
}
LL f[2][MAXN],g[2][MAXN];
int dep[MAXN];
inline void dfs1(int v,int fa=0){
dep[v] = dep[fa]+1;
for(int i = head[v];i;i = e[i].nxt){
if(e[i].to == fa) continue;
dfs1(e[i].to,v);
}
}
bool flag = 0;
int cnt[MAXN];
inline void dfs(int v,int d=0,int fa=0){
++cnt[d];
for(int i = head[v];i;i = e[i].nxt){
if((flag||dep[e[i].to] > dep[v]) && e[i].to != fa) dfs(e[i].to,d+1,v);
}
}
inline void gao1(int rt,int p,LL &f){
CLR(cnt,0);dfs(rt);
FOR(i,0,p) f += 1ll*cnt[i]*(cnt[p-i]-(i==p-i));
for(int i = head[rt];i;i = e[i].nxt){
if(dep[e[i].to] > dep[rt]){
CLR(cnt,0);dfs(e[i].to,1);
FOR(i,0,p) f -= 1ll*cnt[i]*(cnt[p-i]-(i==p-i));
}
}
}
inline void gao2(int rt,int q,LL &g){
CLR(cnt,0);dfs(rt);
FOR(i,0,q) g += 1ll*cnt[i]*(cnt[q-i]-(i==q-i));
for(int i = head[rt];i;i = e[i].nxt){
CLR(cnt,0);dfs(e[i].to,1,rt);
FOR(i,0,q) g -= 1ll*cnt[i]*(cnt[q-i]-(i==q-i));
}
}
int main(){
// freopen("C.in","r",stdin);
// freopen("C.out","w",stdout);
scanf("%d%d%d",&n,&p,&q);
FOR(i,2,n){
int u,v;scanf("%d%d",&u,&v);
add(u,v);
}
dfs1(1);
// f: lca=i
// g: 经过 i
// 0: p
// 1: q
FOR(i,1,n) gao1(i,p,f[0][i]),gao1(i,q,f[1][i]);flag = 1;
FOR(i,1,n) gao2(i,p,g[0][i]),gao2(i,q,g[1][i]);
LL s1 = 0,s2 = 0;
FOR(i,1,n) s1 += f[0][i],s2 += f[1][i];
LL ans = 0;
ans = s1*s2;
FOR(i,1,n) ans -= f[0][i]*g[1][i];
FOR(i,1,n) ans -= g[0][i]*f[1][i];
FOR(i,1,n) ans += f[0][i]*f[1][i];
printf("%lld
",ans);
return 0;
}
D. 小号
最大的构造方案一定是 2 3 4 ... n 1
,答案的上界是 (frac{n(n-1)}{2})。
我们找到最小的 (t),满足 (frac{t(t-1)}{2} geq s),设 (r = frac{t(t-1)}{2}-s)(也就是要减少的数量),那么一定要有 (r leq t-1),我们先将前 (t) 个位置按照 2 3 4 ... t 1
放置,后面的 (a_i=i)。我们考虑先交换 (a_1) 和 (a_t),这样可能会让 (r--) (如果 (t) 是偶数),仍然有 (r leq t-1),然后只需要对应交换 (a_1) 和 (a_r) 就好了,这样会让答案减少 (r-1),交换的位置 (leq t),不会超过 (n)。
构造题还是要考虑如何构造最大值。。然后去减少。这题考虑增加就不是很简单,还要努力分析一下缺少的个数来观察是否可以多交换一些东西扩大操作空间。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1e5 + 5;
int n,a[MAXN];
LL s;
int main(){
scanf("%d%lld",&n,&s);
if(s > 1ll*n*(n-1)/2){
puts("nO 5oLuTi0N");
return 0;
}
LL t = 1;
while(t*(t-1)/2 < s) ++t;
FOR(i,1,t-1) a[i] = i+1;a[t] = 1;
FOR(i,t+1,n) a[i] = i;
LL r = t*(t-1)/2-s;
if(r){
// r < t
r -= !((t&1));
std::swap(a[1],a[t]);
// r <= t
std::swap(a[1],a[r+1]);
}
FOR(i,1,n) printf("%d%c",a[i],"
"[i==n]);
return 0;
}