cable cable cable
- 题意: M个灯,K个盒子,求最少要连多少条线,使任选K个盒子每个灯都能装下
- 思路: 每个灯要连(M-K+1)个 总共M*(M-K+1)
happy happy happy
- 题意: 左右取数,孩子每次都去左右两边最大的那个,父亲想让孩子赢(大于父亲)且最小化分差
- 思路: 限时搜索,先dp预处理出l,r区间最大分差和最小分差,然后\(2^n\)搜索,但每进入一层都要利用dp数组更新一次答案
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e2+10;
int n,a[N],mx[N][N],mn[N][N],ans;
int st,lim = 1000;
void init(){
memset(mx,-0x3f,sizeof mx);
memset(mn,0x3f,sizeof mn);
for(int i=1;i<=n+1;++i) mx[i][i-1] = mn[i][i-1] = 0;
for(int l=n;l>0;--l){
for(int r=l;r<=n;++r){
int ll = l,rr = r,son;
if(a[ll]>=a[rr]) son = a[ll++]; // 儿子取走左边的
else son = a[rr--]; // 取走右边的
mx[l][r] = max(mx[l][r],mx[ll][rr-1]+a[rr]-son); // 取左边
mx[l][r] = max(mx[l][r],mx[ll+1][rr]+a[ll]-son); // 取右边
mn[l][r] = min(mn[l][r],mn[ll][rr-1]+a[rr]-son);
mn[l][r] = min(mn[l][r],mn[ll+1][rr]+a[ll]-son);
}
}
}
void dfs(int l,int r,int dif){
if(l>r){ // 搜索到终点
if(dif<0) ans = max(ans,dif); // dif为当前爸爸比儿子少的
return ;
}
if(mn[l][r]+dif>=0 || mx[l][r]+dif<=ans) return ; // 可行性剪枝与最优化剪枝
if(mx[l][r]+dif<0){
ans = max(ans,mx[l][r]+dif);
return ;
}
if(clock()-st>lim) return ; // 超时停止搜索
int son;
if(a[l]>=a[r]) son = a[l++]; // 儿子优先拿大的
else son = a[r--];
dfs(l+1,r,dif+a[l]-son); // 取左边
dfs(l,r-1,dif+a[r]-son); // 取右边
}
int main(){
while(scanf("%d",&n)==1){
memset(a,0,sizeof a);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
init();
st = clock();
ans = -inf; // 爸爸比儿子少拿的 最后要取反
dfs(1,n,0);
if(ans == -inf) puts("The child will be unhappy...");
else printf("%d\n",-ans);
}
return 0;
}
array array array
- 题意: 一个序列,每次可以选最多k个数抹去,问能否变成一个非递增或非递减的序列
- 思路: dp求最长非递增l1,非递减序列的长度l2,max(l1,l2)+k>=n 就可以
number number number
- 题意: 求不能被k个斐波那契数组合出(可重复)的最小整数.
- 思路: 找规律可得为第k*2+3个斐波那契数,矩阵递推.
#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
using namespace std;
const int inf = 0x3f3f3f3f;
const int MOD = 998244353;
struct mat{
ll a[2][2];
mat operator *(mat b){
mat res; res.a[0][0] = res.a[0][1] = res.a[1][0] = res.a[1][1] = 0;
for(int i=0;i<2;++i){
for(int k=0;k<2;++k){
for(int j=0;j<2;++j){
res.a[i][j] = (a[i][k] * b.a[k][j] %MOD + res.a[i][j])%MOD;
}
}
}
return res;
}
void init(){
a[0][0] = a[0][1] = a[1][0] = 1; a[1][1] = 0;
}
};
mat qpow(mat c,ll b){
mat res; res.a[0][0] = res.a[1][1] = 1; res.a[1][0] = res.a[0][1] = 0;
while(b){
if(b%2) res = res*c;
b>>=1;
c = c*c;
}
return res;
}
int main(){
ll k;
while(scanf("%lld",&k)!=EOF){
mat res; res.init();
res = qpow(res,k*2+3);
printf("%lld\n",(res.a[1][0]-1+MOD)%MOD);
}
return 0;
}
gems gems gems
- 题意: n堆石子从左往右拿,每次可以拿k或k+1堆(k为上一次拿的),A想最大化差异,B想最小化差异,问最后的差异是多少.
- 思路: 可以直接当做每个人都想最大化自己取到的石子,dp[i][j]表示还剩i堆石子,上一个人取了j堆时先手比后手多拿了多少石子
\(dp[i][j] = max(sum[i] - sum[i-j] - dp[i-j][j],sum[i] - sum[i-j-1] - dp[i-j-1][j+1](i>j))\)
因为n为2e4,每次拿k+1,k最大也只能取到sqrt(n).
#include<bits/stdc++.h>
using namespace std;
#define lson(p) (p<<1)
#define rson(p) (p<<1|1)
#define ll long long
const int N = 2e4+10;
int n,sum[N],v[N],f[N][202];
void gmax(int &a,int b){
if(b>a) a = b;
}
int main(){
int t; scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=n;i>=1;--i)// 反向dp,从后往前拿,所以输入要倒序
scanf("%d",&v[i]);
for(int i=1;i<=n;++i){
sum[i] = sum[i-1] + v[i];
}
int m = sqrt(n*2);
while((1+m)*m >n*2) --m;
for(int i=1;i<=n;++i){ // 还剩多少堆
int top = min(i,m);
for(int j=1;j<=top;++j){ // 当前这轮拿j堆
f[i][j] = sum[i] - sum[i-j] - f[i-j][j]; // 下次拿j堆
if(i>j) gmax(f[i][j],sum[i] - sum[i-j-1] - f[i-j-1][j+1]); // 下次拿j+1堆
}
}
printf("%d\n",f[n][1]); // 反向dp f[n][1] 为答案
}
return 0;
}
transaction transaction transaction
- 题意: 一棵树,可以选择一个点出发,走到终点获得\(v[tp]-v[sp] - w\),终点价值-起点价值-路上花费,求最大收益
- 思路: 树形dp,0代表到这个点的最小花费,1代表卖出去的收益.(其实没必要那么麻烦,自己写挫了)
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5+10;
struct node{
int u,v,w,nxt;
}e[N*2];
int head[N],tot;
int dp[N][2],a[N],n; // 0 买 1 卖
void add(int u,int v,int w){
e[tot] = (node){u,v,w,head[u]}; head[u] = tot++;
e[tot] = (node){v,u,w,head[v]}; head[v] = tot++;
}
void init(){
tot = 0; memset(head,-1,sizeof head);
memset(dp,0,sizeof dp);
}
void dfs(int u,int fa){
for(int i=head[u];~i;i=e[i].nxt){
if(e[i].v == fa) continue;
dp[e[i].v][0] = max(-a[e[i].v],dp[u][0]-e[i].w); // 儿子买
dp[e[i].v][1] = a[e[i].v] + dp[u][0] - e[i].w; // 儿子卖给父亲
dfs(e[i].v,u);
dp[u][1] = max(dp[e[i].v][0]+a[u] -e[i].w,dp[u][1]);
dp[u][0] = max(dp[e[i].v][0]-e[i].w,dp[u][0]);
}
}
int main(){
int t; scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
init();
int u,v,w;
for(int i=1;i<n;++i){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
dp[1][0] = -a[1];
dfs(1,0);
int ans = 0;
for(int i=1;i<=n;++i){
ans = max(ans,dp[i][1]);
}
printf("%d\n",ans);
}
}
ping ping ping
- 题意: 一颗树,其中有些点是走不通的,给出Q个点对表示两点之间走不通,问最少有几个点是走不通的
- 思路: 贪心,每个点对,都让他们的lca走不通(lca可以控制最多的点),且先从lca较深的开始(较深的可以切断向较浅去的路径),用树剖求出dfs序及维护lca,L[u]~R[u]表示这个点掌控的范围(不在l~r区间内的点要到l~r必走u),用BIT维护区间修改,单点查询,每次割点就是L[u]+1,R[u]-1,而只要query(u)!=0则说明这个点已经有祖先被割掉了(因为是按lca深度由深到浅,不会出现先割祖先,再割后代,所以被割过就肯定走不通了);
#include<bits/stdc++.h>
using namespace std;
#define lson(p) (p<<1)
#define rson(p) (p<<1|1)
#define ll long long
const int N = 5e5+10;
struct Edge{
int u,v,w,nxt;
}e[N*2];
int head[N],tot;
int top[N],fa[N],deep[N],num[N],L[N],R[N],son[N]; // 重儿子
int pos;
void addedge(int u,int v,int w=0){e[tot] = (Edge){u,v,w,head[u]}; head[u] = tot++;}
struct node{
int u,v,la;
bool operator <(node b){
return deep[la] > deep[b.la];
}
};
vector<node> ve;
//第一遍dfs 处理fa,num,deep,son
void dfs1(int u,int pre,int d){
deep[u] = d;
fa[u] = pre;
num[u] = 1;
for(int i=head[u];~i;i=e[i].nxt){
int v = e[i].v;
if(v!=pre){
dfs1(v,u,d+1);
num[u] += num[v];
if(son[u] == -1 || num[v] > num[son[u]])
son[u] = v;
}
}
}
// 第二遍dfs 处理 top,p,fp
void dfs2(int u,int sp){
top[u] = sp;
L[u] = ++pos;
if(son[u]== -1){
R[u] = pos ;
return ;
}
dfs2(son[u],sp); // 当前链继续走重儿子
for(int i=head[u];i!=-1;i=e[i].nxt){
int v = e[i].v;
if( v!= son[u] && v!=fa[u])
dfs2(v,v); // 以自己为链首的新链
}
R[u] = pos ;
}
int n,m;
struct BIT{
int a[N];
void init(){
memset(a,0,sizeof a);
}
int lowbit(int x){
return x&(-x);
}
void update(int x,int pos){
for(int i=pos;i<=n;i+=lowbit(i)) a[i] += x;
}
int query(int pos){
int res = 0;
for(int i=pos;i;i-=lowbit(i)) res += a[i];
return res;
}
}bt;
int lca(int x,int y){
while(top[x]!=top[y]){
if(deep[top[x]] > deep[top[y]]) x = fa[top[x]];
else y = fa[top[y]];
}
return deep[x] < deep[y] ? x:y;
}
void init(){
memset(head,-1,sizeof(head));memset(son,-1,sizeof(son));
bt.init();ve.clear();tot = 0;pos = 0;
}
int main(){
while(scanf("%d",&n)==1){
int u,v,ans=0;
init();
for(int i=1;i<=n;++i){
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
dfs1(0,0,0); // 题目默认0为根节点
dfs2(0,0);
scanf("%d",&m);
for(int i=1;i<=m;++i){
scanf("%d%d",&u,&v);
ve.push_back({u,v,lca(u,v)});
}
sort(ve.begin(),ve.end());
for(auto item:ve){
u = item.u, v = item.v;
if(bt.query(L[u]) || bt.query(L[v])) continue; // 已经被割掉的点覆盖
bt.update(1,L[item.la]) ; bt.update(-1,R[item.la]+1); // 割去他们的lca
ans++;
}
printf("%d\n",ans);
}
return 0;
}
card card card
- 题意: 两个序列a,b(\(\sum{a}=\sum{b}\)),每次向后走一步,sum+a[i]-b[i],能将最前面的一堆放到最后面,问要移多少堆才能拿到最后一堆
- 思路: 相对位置是不变的,直接求a[i]-b[i]的前缀和,最小即为答案
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e6+10;
int a[N],dp[N];
int n;
int main(){
while(scanf("%d",&n)==1){
int val;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;++i){
scanf("%d",&val); a[i]-=val;
}
int ans = 0,pos=0;
for(int i=1;i<=n;++i){
dp[i] = dp[i-1] + a[i];
if(dp[i]<ans){// 前缀和为负数是移不到最后的,要把最小的那个对应的堆数移到后面,则剩下的肯定全是正的
ans = dp[i];
pos = i;
}
}
printf("%d\n",pos);
}
}
原文地址:https://www.cnblogs.com/xxrlz/p/11688526.html
时间: 2024-10-10 12:38:24