A.点对最大值
题意:
解法:
起始就是dp求树的直径的变形题,但是我用d[x][2]存最大和次大,用d[x][0]+d[x][1]更新答案只能过百分60
改成d[x]记录最大,用d[x]+d[v]+w更新答案就对了
琢磨了一下感觉是d[x][2]的方法没办法判断是否是两个点(一个点的时候也会被计算进去)
ps:
我好像被我自己这种写法坑过一次了,又上当一次。
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
vector<pair<int,int> >g[N];
int a[N],n;
int d[N];//向下延伸只含一个端点
int ans;
void dfs(int x,int fa){
d[x]=a[x];//fir
for(auto i:g[x]){
int v=i.first,w=i.second;
if(v==fa)continue;
dfs(v,x);
ans=max(ans,d[x]+d[v]+w);
d[x]=max(d[x],d[v]+w);
}
}
signed main(){
int T;cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++){
g[i].clear();
}
for(int i=2;i<=n;i++){
int x,w;cin>>x>>w;
g[x].push_back({i,w});
g[i].push_back({x,w});
}
for(int i=1;i<=n;i++){
cin>>a[i];
}
ans=-1e18;
dfs(1,1);
cout<<ans<<endl;
}
return 0;
}
B.减成一
题意:
数据范围:n<=1e5
解法:
每次操作可以将一个区间内的数减一。
可以将操作转化为差分数组操作,在差分数组中,操作可以转为先选取一个数减一再选取一个数加一。
那么问题变为要将差分数组第一个数修改为1,其余数为0,问最少操作次数。
最快操作方式就是将差分数组第一个数减为1,其余减为0。
因为保证有解,所以答案为差分数组的正数之和减一。
ps:
转化为差分数组似乎有点降维的意思
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],n;
int b[N];
signed main(){
int T;cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
b[i]=a[i]-a[i-1];
}
int ans=0;
for(int i=1;i<=n;i++){
if(b[i]>0){
ans+=b[i];
}
}
cout<<ans-1<<endl;
}
return 0;
}
D.扔硬币
题意:
在模1e9+7意义下计算答案
数据范围:n<=1e5,k<=1e3,k<=n
解法:
已知至少m个反面,恰好k个正面,那么至少m+k个是确定的,如果m+k>n,那么无解,直接输出0
这题的概率选择用合法方案除以总方案来计算
合法方案为组合数C(n,k)
因为题目已知至少有m枚是反面,所以总方案数为2n-sum(C(n,i)),其中i<m,减去sum(C(n,i))是减去少于m个是反面的情况
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=1e5+5;
int fac[N];
int inv[N];
int n,m,k;
int ppow(int a,int b,int mod){
int ans=1%mod;a%=mod;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
int C(int n,int m){
if(m<0||m>n)return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
fac[0]=1;
for(int i=1;i<N;i++){
fac[i]=fac[i-1]*i%mod;
}
inv[N-1]=ppow(fac[N-1],mod-2,mod);
for(int i=N-2;i>=0;i--){
inv[i]=inv[i+1]*(i+1)%mod;
}
int T;cin>>T;
while(T--){
cin>>n>>m>>k;
if(m+k>n){
cout<<0<<endl;
continue;
}
int ans=ppow(2,n,mod);
for(int i=0;i<m;i++){
ans=(ans-C(n,i)+mod)%mod;
}
ans=C(n,k)*ppow(ans,mod-2,mod)%mod;
cout<<ans<<endl;
}
return 0;
}
G.养花
题意:
解法:
采用网络流直接进行建图,对于四种操作,每次都新建两个点from和to,
from->to边权为c
[a1,a2]区间每个节点连接到from节点,边权为inf
to连接[b1,b2]区间的每个节点,边权为inf
跑最大流即可。
ps:
如果数据范围大的话,还需要使用线段树来优化区间建图
code:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N=2e4+5;
const int M=1e6+5;
int head[N],nt[M],to[M],w[M],cnt;
int idx[N],tot;
int d[N];
int n,m,k;
void init(){
memset(head,0,sizeof head);
cnt=1;
tot=0;
}
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(int st,int ed){
for(int i=1;i<=tot;i++)d[i]=0;
d[st]=1;
queue<int>q;
q.push(st);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(w[i]&&!d[v]){
d[v]=d[x]+1;
q.push(v);
if(v==ed)return 1;
}
}
}
return 0;
}
int dfs(int x,int ed,int flow){
if(x==ed)return flow;
int res=flow;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,ed,min(res,w[i]));
w[i]-=k;
w[i^1]+=k;
res-=k;
if(!k)d[v]=-1;
if(!res)break;
}
}
return flow-res;
}
int dinic(int st,int ed){
int ans=0;
while(bfs(st,ed))ans+=dfs(st,ed,1e9);
return ans;
}
signed main(){
int T;cin>>T;
while(T--){
init();
cin>>n>>m>>k;
int st=++tot;//源点
int ed=++tot;//汇点
for(int i=1;i<=k;i++){
idx[i]=++tot;
}
//k高度连接汇点
add(idx[k],ed,1e9);//k->ed容量无限
add(ed,idx[k],0);
//
for(int i=1;i<=n;i++){
int hi;cin>>hi;
add(st,idx[hi],1);
add(idx[hi],st,0);
}
while(m--){
int op,c;cin>>op>>c;
int from=++tot;
int to=++tot;
add(from,to,c);
add(to,from,0);
if(op==1){//l->r
int l,r;cin>>l>>r;
add(idx[l],from,1e9);
add(from,idx[1],0);
add(to,idx[r],1e9);
add(idx[r],to,0);
}else if(op==2){//[l,r]->x
int l,r,x;cin>>l>>r>>x;
for(int i=l;i<=r;i++){
add(idx[i],from,1e9);
add(from,idx[i],0);
}
add(to,idx[x],1e9);
add(idx[x],to,0);
}else if(op==3){//x->[l,r]
int x,l,r;cin>>x>>l>>r;
add(idx[x],from,1e9);
add(from,idx[x],0);
for(int i=l;i<=r;i++){
add(to,idx[i],1e9);
add(idx[i],to,0);
}
}else if(op==4){//[l,r]->[lc,rc]
int l,r,lc,rc;cin>>l>>r>>lc>>rc;
for(int i=l;i<=r;i++){
add(idx[i],from,1e9);
add(from,idx[i],0);
}
for(int i=lc;i<=rc;i++){
add(to,idx[i],1e9);
add(idx[i],to,0);
}
}
}
int ans=dinic(st,ed);
cout<<ans<<endl;
}
return 0;
}
I.字典序
题意:
解法:
对于a[i]位置的数据分一下三种情况进行讨论:
1.若a[i] == a[i+1],那么删除a[i]后与删除a[i + 1]的结果一样,那么a[i]出处的字典序会比a[i+1]出小,
2.若a[i] < a[i+1],那么删除a[i]后的数组在第i位上比删除a[i+1]所得到的更大,所以第i个位置的字典序比后面位置的字典序都小
3. 若a[i] > a[i+1],那么删除a[i]后的数组在第i位上比删除a[i+1]所得到的更小,所以第i个位置的字典序比后面位置的字典序都大。
上述情况的处理有一个很好的办法:
定义一个双端队列deque
从后向前遍历,如果i比后面的都小,就放到队尾;如果i比后面的都大,就放到队头。
但是发现等于的情况在这里不好处理,因为要插入队列中间,
不过因为等于的几个数直接从小到大输出就行了,那么将等于的合并,输出的时候再将合并的下标从小到大输出就行了。
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int>g[N];
int a[N];
signed main(){
int T;cin>>T;
while(T--){
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
g[i].clear();
}
deque<int>q;
q.push_front(n);
for(int i=n-1;i>=1;i--){
if(a[i]==a[i+1]){//将等于的合并,输出的时候从小到大输出就行了
int j=i;
while(j>=1&&a[j]==a[i]){
g[i+1].push_back(j);//合并到i+1
j--;
}
j++;
i=j;
}else if(a[i]>a[i+1]){//i<后面全部
q.push_back(i);
}else if(a[i]<a[i+1]){//i>后面全部
q.push_front(i);
}
}
while(!q.empty()){
int x=q.back();
q.pop_back();
int len=g[x].size();
for(int i=len-1;i>=0;i--){
cout<<g[x][i]<<' ';
}
cout<<x<<' ';
}
cout<<endl;
}
return 0;
}