C. Klee in Solitary Confinement
思路:
首先,对于每个数我们要将其存储在数组中,注意向右位移2e6,而不是1e6,因为k的取值也可能是负数。
其次,对于每一个位置上的值是否操作,我们要进行讨论,优先将其变到x+k的位置上去,然后这个数的数目要-1,即:dp[x+k]++,dp[x]--,并且dp[x]=max(0,dp[x])保证其非负。
最后在每次要对答案取max,特判当k=0时,即不操作,此时最多的次数即为答案。
C
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
const int N=6e6+5;
int a[N],dp[N],num[N];
int main(){
int n,m=2e6,k;
scanf("%d%d",&n,&k);
int ans=0;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
num[a[i]+m]++;
ans=max(ans,num[a[i]+m]);
}if(k==0){
cout<<ans<<"\n";
return 0;
}
for(int i=0;i<n;i++){
int x=a[i]+m;
dp[x+k]++;
dp[x]--;
dp[x]=max(0,dp[x]);
ans=max(ans,dp[x+k]+num[x+k]);
}cout<<ans<<"\n";
}
思路:
树形dp,定义dp状态,dp1表示以此节点为根节点所能取得的值,dp2表示以此节点为根节点但不包括其孩子结点所能取得的值。
那么进行如下讨论:
1.如果节点x的孩子结点之间没有t[y]=3的节点,那么此节点,
2.讨论如果结点x的孩子结点中有t[y]=3的节点,那么我们可以操作,先去吃其他节点z,然后立马返回来吃为3的结点y,则结点z的孩子结点则会消失,那么节点z的贡献为dp2[z],则
发现与1相比,此处的变化的量为dp2[z]-dp1[z]+a[z],因此我们只需要对于每个子树都算出变化的量,然后选出最大的两个,至于为什么是最大的两个,我们可以发现,此处变化的量可能与a[y]所对应的结点是一致的,那么就会导致错误,因此我们在进行比较的时候要确定,此节点的变化的量是否为最大的,若为最大的则此节点只能加上第二大的值进行比较即可。
所以综上,我们在对每一个结点的dp1的值进行更新的时候,要分别讨论孩子结点的t[y]是否为3,优先处理出,然后当t[y]=3时,
两个公式进行讨论后,选择其一即可。
H
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define si size()
const int N=1e5+5;
ll a[N],dp1[N],dp2[N];
int t[N];
int n;
vector<int> v[N];
void dfs(int p,int fa){
dp2[p]=a[p];
ll maxn1=-1,maxn2=-1;
for(int i=0;i<v[p].si;i++){
int k=v[p][i];
if(k==fa)continue;
dfs(k,p);
dp2[p]+=dp1[k]-a[k];
ll tp=dp2[k]-dp1[k]+a[k];
if(tp>maxn1)maxn2=maxn1,maxn1=tp;
else if(tp>maxn2)maxn2=tp;
}dp1[p]=dp2[p];
for(int i=0;i<v[p].si;i++){
int k=v[p][i];
if(k==fa)continue;
dp1[p]=max(dp1[p],dp2[p]+a[k]);
if(t[k]==3){
if(dp2[k]-dp1[k]+a[k]==maxn1)dp1[p]=max(dp1[p],dp2[p]+a[k]+maxn2);
else dp1[p]=max(dp1[p],dp2[p]+a[k]+maxn1);
}
}
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),dp1[i]=dp2[i]=0;
for(int i=1;i<=n;i++)scanf("%d",&t[i]),v[i].clear();
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
v[x].pb(y);v[y].pb(x);
}dfs(1,0);
cout<<dp1[1]<<"\n";
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}
组合数基础题
思路:
我们可以枚举选择a的个数x,然后可以算出所要求的的n位数的和即s=a*x+(n-x)*b;
然后我们判断s是否good数,如果s是好数,那么我们可以进行计算选x个a对应的组合数,然后加到ans里面。
注意此处计算组合数,由于题目n的范围过大1e6,所以我们不能采用递推求解C数组,因此我们此处采用求逆元的方法来求组合数,预处理出阶乘,然后和阶乘的逆元即可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
const int N=2e6+5;
const int mod=1e9+7;
ll jp[N],inv[N];
int n,a,b;
ll qpow(ll x,ll k){
ll res=1;
while(k){
if(k&1)res=res*x%mod;
x=x*x%mod;
k/=2;
}return res;
}
void pre(){
jp[0]=1;
for(int i=1;i<=n;i++)jp[i]=jp[i-1]*i%mod;
inv[0]=inv[1]=1;
for(int i=2;i<=n;i++)inv[i]=qpow(jp[i],mod-2);
}
int check(int x){
while(x){
int k=x%10;
if(k!=a&&k!=b)return 0;
x/=10;
}return 1;
}
int main(){
cin>>a>>b>>n;
ll ans=0;
pre();
for(int i=0;i<=n;i++){
int num=i*a+(n-i)*b;
if(check(num)){
ans=(ans+((jp[n]*inv[i])%mod*inv[n-i])%mod)%mod;
}
}cout<<ans<<"\n";
}
前缀和题目:
思路:
因为所有的操作都是在数组内部进行,因此不影响整个数组的和,而且对于每一次操作而言,改变的是连续的三个数,假设i-1,i,i+1,用s表示前缀和数组;
在一次的操作中,s[i+1]的值是不会变的,而s[i-1]的值会更新为原来的s[i],而s[i]的值会更新为原来的s[i-1]的值,
相当于经过一次操作s[i-1]和s[i]会互相交换,且我们可以进行任意次交换,那么表明除了s[0]和s[n]以外其余的s可以到达任意位置。
因此题目转化为将s数组中除了0和n的位置不变其余的位置任意交换,求出最小的(s[i]-s[i-1])的值
假设情况s[0],s[n]分别是最小的和最大的,那么我们对s数组进行排序,一次填充即可。
其他
可能s[0]不是最小,s[n]不是最大,那么我们可以从s[0]开始隔一个取,一直取到min,然后从s[n]开始隔一个取,一直到max,然后做好取数的标记,然后在遍历一遍数组,对没有取出的数,进行加入即可。
这样能保证max(s[i]-s[i-1])是最小的。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=3e5+5;
ll a[N],s[N];
int vis[N];
void solve(){
int n;
cin>>n;
s[0]=vis[0]=0;
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]+=s[i-1];
vis[i]=0;
}
ll s0=0,sn=s[n];
sort(s,s+n+1);
int l=0,r=n;
for(int i=lower_bound(s,s+n+1,s0)-s;i>=0;i-=2){
a[l++]=s[i];
vis[i]=1;
}for(int i=lower_bound(s,s+n+1,sn)-s;i<=n;i+=2){
a[r--]=s[i];
vis[i]=1;
}for(int i=0;i<=n;i++){
if(!vis[i])a[l++]=s[i];
}ll ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,abs(a[i]-a[i-1]));
}cout<<ans<<"\n";
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
}