The 8th Hebei Collegiate Programming Contest
签到
题意:
定义区间 [ l , r ] 的值为 m a x ( a l , . . . a r ) ⋅ m i n ( a l , . . . , a r ) ⋅ ( r − l + 1 ) , 找第 k 大的值 定义区间 [l,r]的值为max(a_l ,...a_r) \cdot min(a_l,...,a_r) \cdot (r-l+1),找第k大的值 定义区间[l,r]的值为max(al,...ar)⋅min(al,...,ar)⋅(r−l+1),找第k大的值
思路:
单调栈做法,参考自jiangly,据他分析,在二分区间另外一个端点时,先倍增再二分可以做到两个log
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
struct RMQ{
vector<vector<int>> st;
int n;
void init(vector<int> &a,int n){
this->n=n;
st=vector<vector<int>> (n+1,vector<int> (30,1e9));
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;j<=25;j++){
for(int i=1;i+(1<<j)-1<=n;i++)st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
int query(int l,int r){
int len=__lg(r-l+1);
return max(st[l][len],st[r-(1<<len)+1][len]);
}
} rmq;
void solve(){
int n,k;
cin>>n>>k;
vector<int> a(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
vector<int> l(n+1),r(n+1,n+1);
vector<int> st;
iota(l.begin(),l.end(),0);
iota(r.begin(),r.end(),0);
for(int i=2;i<=n;i++){
while(l[i]>1&&a[i]<a[l[i]-1])
l[i]=l[l[i]-1];
}
for(int i=n-1;i>=1;i--){
while(r[i]<n&&a[i]<=a[r[i]+1])r[i]=r[r[i]+1];
}
rmq.init(a,n);
auto get=[&](int l,int r,int mn){
if(r>n||l<1)return INF*INF;
return rmq.query(l,r)*mn*(r-l+1);
};
auto count_l=[&](int l,int r,int L,int R,int mn,int mid){
int ans=0;
// for(int i=l,j=L;i<=r;i++){
// while(j<=R&&get(i,j,mn)
// ans+=R-j+1;
// }
for(int i=l,j=L;i<=r;i++){
// int t=1;
// while(j+t<=R&&get(i,j+t,mn)
int lo=j,hi=R+1;
while(lo<hi){
int m=lo+hi>>1;
if(get(i,m,mn)>=mid)hi=m;
else lo=m+1;
}
j=lo;
ans+=R-j+1;
if(j==R+1)break;
}
return ans;
};
auto count_r=[&](int l,int r,int L,int R,int mn,int mid){
int ans=0;
// for(int i=R,j=r;i>=L;i--){
// while(j>=l&&get(j,i,mn)
// ans+=j-l+1;
// }
for(int i=R,j=r;i>=L;i--){
// int t=1;
// while(j-t>=l&&get(j-t,i,mn)
int lo=l-1,hi=j;
while(lo<hi){
int m=lo+hi+1>>1;
if(get(m,i,mn)>=mid)lo=m;
else hi=m-1;
}
j=lo;
ans+=j-l+1;
if(j==l-1)break;
}
return ans;
};
auto check=[&](int mid){
int ans=0;
for(int i=1;i<=n;i++){
if(i-l[i]+1<=r[i]-i+1)ans+=count_l(l[i],i,i,r[i],a[i],mid);
else ans+=count_r(l[i],i,i,r[i],a[i],mid);
}
return ans>=k;
};
int L=1,R=1e18;
while(L<R){
int mid=L+R+1>>1;
if(check(mid))L=mid;
else R=mid-1;
}
cout<<L<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
笛卡尔树分治写法,大差不差,但是需要注意剪枝,不然会tle
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=5e4+10,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
struct RMQ{
vector<vector<int>> st;
int n;
void init(int a[],int n){
this->n=n;
st=vector<vector<int>> (n+1,vector<int> (30,1e9));
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;j<=25;j++){
for(int i=1;i+(1<<j)-1<=n;i++)st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
int query(int l,int r){
int len=__lg(r-l+1);
return max(st[l][len],st[r-(1<<len)+1][len]);
}
} rmq;
int stk[N],ls[N],rs[N],top,cur;
int n,k,a[N];
int get(int l,int r,int mn){
if(l<1||r>n)return INF*INF;
return rmq.query(l,r)*mn*(r-l+1);
}
void dfs(int u,int l,int r,int x,int &ans){
if(u-l<r-u){
for(int i=l;i<=u;i++){
int lo=u,hi=r+1;
while(lo<hi){
int mid=lo+hi>>1;
if(get(i,mid,a[u])>=x)hi=mid;
else lo=mid+1;
}
ans+=r+1-lo;
if(lo==r+1)break;
}
}
else{
for(int i=r;i>=u;i--){
int lo=l-1,hi=u;
while(lo<hi){
int mid=lo+hi+1>>1;
if(get(mid,i,a[u])>=x)lo=mid;
else hi=mid-1;
}
ans+=lo-l+1;
if(lo==l-1)break;
}
}
if(ls[u])dfs(ls[u],l,u-1,x,ans);
if(rs[u])dfs(rs[u],u+1,r,x,ans);
}
bool check(int mid){
int ans=0;
dfs(stk[1],1,n,mid,ans);
return ans>=k;
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
rmq.init(a,n);
for(int i=1;i<=n;i++){
cur=top;
while(cur&&a[stk[cur]]>a[i])cur--;
if(cur)rs[stk[cur]]=i;
if(cur<top)ls[i]=stk[cur+1];
stk[++cur]=i;
top=cur;
}
int l=1,r=1e18;
while(l<r){
int mid=l+r+1>>1;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
思路:
贪心把当前能选的人加进来,然后选择r最小的,用堆维护
卡long long的sb题
#include
#define LL long long
using namespace std;
typedef pair<int,int> PII;
const int N=1<<11;
const int INF=1e9;
const int mod=1e9+7;
void solve(){
int n;
vector<array<int,3>> a;
cin>>n;
for(int i=1;i<=n;i++){
int l,r;
cin>>l>>r;
a.push_back({l,r,i});
}
sort(a.begin(),a.end());
priority_queue<array<int,2>,vector<array<int,2>>,greater<array<int,2>>> q;
vector<int> nums(n+1);
int i=0,cnt=0;
while(true){
while(i<n&&a[i][0]<=cnt)q.push({a[i][1],a[i][2]}),i++;
if(q.empty())break;
while(!q.empty()){
auto [r,id]=q.top();
q.pop();
if(cnt>r)continue;
nums[id]=++cnt;
break;
}
}
vector<array<int,2>> tmp;
for(int i=1;i<=n;i++){
if(nums[i]!=0)tmp.push_back({nums[i],i});
}
sort(tmp.begin(),tmp.end());
cout<<tmp.size()<<"\n";
for(auto [x,y]:tmp)cout<<y<<" ";
cout<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
给定只含有c和p两种字符的串,进行恰好m次操作,使得子序列为ccpc或者ppcp的字符最多?
每次操作可以交换相邻字符
数据范围: 1 ≤ ∣ S ∣ , n ≤ 500 1 \leq |S|,n \leq 500 1≤∣S∣,n≤500
思路:
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
int f[N][N][N],pos[N];
void solve(){
int n,m;
string s;
cin>>s>>m;
n=s.size();
s=" "+s;
int numc=0,nump=0;
for(int i=1;i<=n;i++){
if(s[i]=='c')numc++;
else pos[++nump]=i;
}
memset(f,-0x3f,sizeof f);
f[0][0][0]=0;
for(int i=0;i<n;i++){
for(int j=0;j<=min(numc,i);j++){
for(int k=0;k<=m;k++){
if(f[i][j][k]<0)continue;
if(j<numc){
int lp=i-j;
if(nump>=lp)f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+lp*(lp-1)/2*(nump-lp));
}
int tmp=nump-(i-j);
if(tmp&&k+abs(pos[i-j+1]-i-1)<=m){
f[i+1][j][k+abs(pos[i-j+1]-i-1)]=max(f[i+1][j][k+abs(pos[i-j+1]-i-1)],f[i][j][k]+j*(j-1)/2*(numc-j));
}
}
}
}
int ans=0;
for(int i=m;i>=0;i-=2){
ans=max(ans,f[n][numc][i]);
}
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
方法二的解法,状态转移差不多
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
void solve(){
int n,m;
string s;
cin>>s>>m;
n=s.size();
s=" "+s;
vector<int> pre(n+1);
for(int i=1;i<=n;i++)pre[i]=pre[i-1]+(s[i]=='c');
vector dp(2,vector(pre[n]+1,vector(m+1,-INF)));
dp[0][0][0]=0;
for(int i=0;i<n;i++){
swap(dp[0],dp[1]);
dp[0].assign(pre[n]+1,vector(m+1,-INF));
for(int x=0;x<=pre[n];x++){
for(int y=0;y<=m;y++){
if(dp[1][x][y]<0)continue;
int ny=y+abs(x-pre[i+1]);
if(ny<=m){
dp[0][x][ny]=max(dp[0][x][ny],dp[1][x][y]+x*(x-1)/2*(pre[n]-x));//选p
}
ny=y+abs(x+1-pre[i+1]);
if(ny<=m&&x+1<=pre[n])dp[0][x+1][ny]=max(dp[0][x+1][ny],dp[1][x][y]+(i-x)*(i-x-1)/2*(n-pre[n]-(i-x)));//选c
}
}
}
int ans=0;
for(int i=m;i>=0;i-=2)ans=max(ans,dp[0][pre[n]][i]);
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
给定三个食堂的坐标,n个学生的坐标和办公室坐标,以及包子和鸡蛋限购数
量和总共需要采购的数量,求所有学生总路程最少是多少。
思路:
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=3010;
const int INF=1e9;
const int mod=1e9+7;
long double dp[2][N],w[N][1<<3];
pair<long double,long double> c[4];
vector<pair<long double,long double>> tmp;
int h[10],vis[10];
int n,m,k,b,e;
long double dis(long double x1,long double y1,long double x2,long double y2){
return sqrtl((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
void dfs(int x,int y){
if(x==y+1){
int state=0;
for(int i=1;i<x;i++)state|=1<<h[i];
for(int i=1;i<=k;i++){
long double ans=0;
long double xx=tmp[i].first,yy=tmp[i].second;
for(int j=1;j<x;j++){
auto [x,y]=c[h[j]];
ans+=dis(xx,yy,x,y);
xx=x,yy=y;
}
if(y!=0)ans+=dis(xx,yy,c[3].first,c[3].second);
w[i][state]=min(w[i][state],ans);
}
return;
}
for(int i=0;i<3;i++){
if(vis[i])continue;
vis[i]=1;
h[x]=i;
dfs(x+1,y);
vis[i]=0;
h[x]=-1;
}
}
int get(int st){
int cnt=0;
for(int i=0;i<3;i++)cnt+=(st>>i&1);
return cnt;
}
void solve(){
cin>>n>>m>>k>>b>>e;
int mx=max((n+b-1)/b,(m+e-1)/e);
for(int i=0;i<4;i++)cin>>c[i].first>>c[i].second;
tmp.emplace_back();
for(int i=1;i<=k;i++){
long double x,y;
cin>>x>>y;
tmp.push_back({x,y});
}
memset(h,-1,sizeof h);
for(int i=1;i<=k;i++){
for(int j=0;j<1<<3;j++)w[i][j]=1e18;
}
for(int i=0;i<2;i++){
for(int u=0;u<=mx;u++)dp[i][u]=1e18;
}
dp[0][0]=0;
for(int i=0;i<4;i++)dfs(1,i);
// for(int i=0;i<8;i++)cout<
for(int i=1;i<=k;i++){
for(int j=0;j<=mx;j++)dp[i&1][mx]=1e18;
for(int st=0;st<1<<3;st++){
int val=get(st);
for(int j=mx;j>=val;j--)dp[i&1][j]=min(dp[i&1][j],dp[i-1&1][j-val]+w[i][st]);
}
}
cout<<setprecision(15)<<fixed;
cout<<dp[k&1][mx]<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
把一个竞赛图中的点分成A、B、C三个非空集合,要求所有的点对满足:
若x∈A,y∈B,则连边顺序为x→y;
若x∈B,y∈C,则连边顺序为x→y;
若x∈C,y∈A,则连边顺序为x→y。
构造一个合法划分方法,或者输出无解。
思路:
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=1010,M=2e6+10;
const int INF=1e9;
const int mod=1e9+7;
int h[N],e[M],ne[M],idx,n;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool mark[N];
int stk[N],top;
int rev(int u){
if(u>n)return u-n;
return u+n;
}
bool dfs(int u){
if(mark[rev(u)])return false;
if(mark[u])return true;
mark[u]=true;
stk[++top]=u;
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(!dfs(v))return false;
}
return true;
}
int a[510][510];
void solve(){
cin>>n;
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++)cin>>a[i][j];
}
vector<int> type(n+1);//0属于集合C,1属于集合B
for(int i=2;i<=n;i++){
type[i]=a[1][i];
}
auto cal=[&](){
memset(h,-1,sizeof h);
for(int i=2;i<n;i++){
for(int j=i+1;j<=n;j++){
int x=i,y=j;
if(a[x][y]==0)swap(x,y);
if(type[x]==1&&type[y]==1){
add(x+n,y+n);
add(y,x);
}
else if(type[x]==0&&type[y]==0){
add(x,y);
add(y+n,x+n);
}
else if(type[x]==1&&type[y]==0){
add(x,y);
add(y+n,x+n);
add(x+n,y+n);
add(y,x);
}
else{
add(x+n,y);
add(y+n,x);
}
}
}
for(int i=2;i<=n;i++){
if(!mark[i]&&!mark[rev(i)]){
top=0;
if(!dfs(rev(i))){
while(top>0)mark[stk[top--]]=0;
if(!dfs(i))return false;
}
}
}
vector<int> SET[3];
for(int i=1;i<=n;i++){
if(i==1||mark[i])SET[0].push_back(i);
else if(type[i]==1)SET[1].push_back(i);
else SET[2].push_back(i);
}
for(int i=0;i<3;i++){
if(SET[i].size()==0)return false;
}
for(int i=0;i<3;i++)cout<<SET[i].size()<<" \n"[i==2];
for(int i=0;i<3;i++){
for(auto it:SET[i])cout<<it<<" ";
cout<<"\n";
}
return true;
};
if(cal())return;
cout<<"0 0 0\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
给定00,01,11珠子的个数,以及一个目标字符串(首尾相接),问能最长串
出多长的区间(01可以翻转成10)。
∣ s ∣ ≤ 1 0 6 |s|≤10^6 ∣s∣≤106
思路:
#include
#define LL long long
using namespace std;
typedef pair<int,int> PII;
const int N=1<<11;
const int INF=1e9;
const int mod=1e9+7;
void solve(){
vector<int> c(3);
for(int i=0;i<3;i++)cin>>c[i];
string s;
cin>>s;
int n=s.size();
s+=s;
int ans=0;
for(int t=0;t<2;t++){
vector cnt=c;
for(int i=t,j=t;i<n;i+=2){
while(j+2<=i+n&&cnt[s[j]-'0'+s[j+1]-'0']>0){
cnt[s[j]-'0'+s[j+1]-'0']--;
j+=2;
}
ans=max(ans,j-i);
cnt[s[i]-'0'+s[i+1]-'0']++;
}
}
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
在一棵树上有一些点有单车,经过有单车的点速度就可以从1变成2,多次询
问从树上点x到点y的最短耗时。
n ≤ 5 × 1 0 5 n≤5×10^5 n≤5×105
思路:
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=5e5+10;
const int INF=1e9;
const int mod=1e9+7;
vector<int> adj[N];
int d[N],par[N][30],mnl[N][30],mnr[N][30],dep[N];
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
par[u][0]=fa;
mnl[u][0]=3*d[fa]-dep[fa];
mnr[u][0]=3*d[fa]+dep[fa];
for(int i=1;i<=__lg(dep[u]);i++){
par[u][i]=par[par[u][i-1]][i-1];
mnl[u][i]=min(mnl[u][i-1],mnl[par[u][i-1]][i-1]);
mnr[u][i]=min(mnr[u][i-1],mnr[par[u][i-1]][i-1]);
}
for(auto v:adj[u]){
if(v==fa)continue;
dfs(v,u);
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
while(dep[x]>dep[y]){
x=par[x][(int)__lg(dep[x]-dep[y])];
}
if(x==y)return x;
for(int i=__lg(dep[x]);i>=0;i--){
if(par[x][i]!=par[y][i]){
x=par[x][i];
y=par[y][i];
}
}
return par[x][0];
}
int dis(int x,int y){
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
//3d[i]+dis[x][y]+(dep[x]-dep[i])=dis[x][y]+dep[x]+(3d[i]-dep[i])
int getl(int x,int y){
int len=dep[x]-dep[y];
int mn=3*d[x]-dep[x];
for(int i=30;i>=0;i--){
if(len>>i&1){
mn=min(mn,mnl[x][i]);
x=par[x][i];
}
}
return mn;
}
//3d[i]+dis[x][y]+(dis[x][y]-(dep[y]-dep[i]))=2dis[x][y]-dep[y]+(3d[i]+dep[i])
int getr(int x,int y){
int len=dep[x]-dep[y];
int mn=3*d[x]+dep[x];
for(int i=30;i>=0;i--){
if(len>>i&1){
mn=min(mn,mnr[x][i]);
x=par[x][i];
}
}
return mn;
}
void solve(){
int n,k;
cin>>n>>k;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
adj[u].push_back(v);
adj[v].push_back(u);
}
queue<int> q;
for(int i=1;i<=n;i++)d[i]=-1;
for(int i=1;i<=k;i++){
int x;
cin>>x;
d[x]=0;
q.push(x);
}
while(!q.empty()){
int u=q.front();
q.pop();
for(auto v:adj[u]){
if(d[v]==-1){
d[v]=d[u]+1;
q.push(v);
}
}
}
dfs(1,0);
int Q;
cin>>Q;
while(Q--){
int x,y;
cin>>x>>y;
int LCA=lca(x,y);
int D=dis(x,y);
int L=D+dep[x]+getl(x,LCA);
int R=2*D-dep[y]+getr(y,LCA);
cout<<min({L,R,2*D})<<"\n";
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
签到
签到,贪心选,快速幂+等比数列求和优化
签到