题目链接
题意:有n个飞机,第i个飞机第i分钟起飞,延误一分钟损失v[i]。
现因不可抗力,前k分钟不能起飞。求一种起飞顺序方案,使总损失最低。
思路:贪心。
总损失=∑(i-a[i].id)*a[i].v=∑(i *a[i].v)-∑(a[i].id *a[i].v)
而后面的∑(a[i].id *a[i].v)是固定已知的,所以只需要∑(i *a[i].v)最小即可。
贪心方法是按a[i].v,用大根堆维护,每次取堆顶飞机起飞。
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define Inf 0x7fffffff
typedef long long ll;
const int N=300007;
ll ans;
struct node{
ll id,v;
bool operator <(const node &o)const{
return v<o.v;
}
}a[N];
int n,k,order[N];
priority_queue<node>q;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i].v);
a[i].id=i;
}
for(int i=1;i<=k;i++)q.push(a[i]);
for(int i=k+1;i<=k+n;i++){
if(i<=n)q.push(a[i]);
node t=q.top();q.pop();
ans+=(i-t.id)*t.v;
order[t.id]=i;
}
cout<<ans<<endl;
for(int i=1;i<=n;i++)printf("%d ",order[i]);
}
题意:有n个人分别在1~n的点上,有m个航班, d u, v, c 表示第d天从u到v的花费为c, 其中u, v 必有一个是0。 要让这n个人到0号点并且所有人在一起的天数至少为k天, 之后再回到各自的点。 输出最小花费, 如果不能满足输出-1。
思路:设i为最晚到达时间,i+k+1是最早离开时间。 找第i天以及之前所有人到达的最小和加上第i+k+1天开始往后所有人离开的最小和。
将所有航班按照日期排序, 正着扫一遍, 就可以维护第i天所有人到达0号点的最小和。 倒着扫一遍, 就是所有人第i天开始离开的最小和。
最后枚举i,即可得到结果
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=100007,M=1000007;
const ll Inf=1ll<<60;
int n,m,k;
ll in[M],out[M],flag[N],all;
struct node{
int d,u,v;
ll c;
bool operator<(const node &o)const{
return d<o.d;
}
}f[N];
int main(){
cin>>n>>m>>k;
for(int i=1;i<=m;i++)scanf("%d%d%d%lld",&f[i].d,&f[i].u,&f[i].v,&f[i].c);
sort(f+1,f+1+m);
for(int i=1;i<=1e6;i++)in[i]=out[i]=Inf;
for(int i=1;i<=m;i++){//前缀,到达的航班
if(f[i].u==0)continue;
if(flag[f[i].u]==0){
flag[0]++;
flag[f[i].u]=f[i].c;
all+=f[i].c;
}else{
all-=flag[f[i].u];
flag[f[i].u]=min(flag[f[i].u],f[i].c);
all+=flag[f[i].u];
}
if(flag[0]==n)in[f[i].d]=all;//n个人都到达了才更新
}
for(int i=2;i<=1e6;i++)in[i]=min(in[i],in[i-1]);//维护前缀最小值
memset(flag,0,sizeof(flag));
all=0;
for(int i=m;i;i--){//后缀,离开的航班
if(f[i].v==0)continue;
if(flag[f[i].v]==0){
flag[0]++;
flag[f[i].v]=f[i].c;
all+=f[i].c;
}else{
all-=flag[f[i].v];
flag[f[i].v]=min(flag[f[i].v],f[i].c);
all+=flag[f[i].v];
}
if(flag[0]==n)out[f[i].d]=all;//n个人都到达了才更新
}
for(int i=1e6-1;i;i--)out[i]=min(out[i],out[i+1]);//维护后缀最小值
ll ans=Inf;
for(int i=1;i<=1e6-k-1;i++)
if(in[i]!=Inf&&out[i+k+1]!=Inf)
ans=min(ans,in[i]+out[i+k+1]);
if(ans<Inf)cout<<ans;
else cout<<-1;
}
题意:按照时间顺序发生q次三种事件:
1.编号x的app收到了一条信息
2.看完编号x的app的所有信息
3.看完从第一条信息开始计数的前k条信息(不是前k条未读信息,而是前k条信息)。
在每次事件结束之后输出一次当前总的未读信息数
思路:给每个app开一个队列维护,1进队,2清空队列,3判断队首是否出队。
每个元素最多进队一次出队一次,所以复杂度O(q)
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define Inf 0x7fffffff
typedef long long ll;
const int N=300007;
int n,q,all,cnt,note[N],pre;
deque<int>a[N];
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>q;
int opt,t;
for(int i=1;i<=q;i++){
scanf("%d%d",&opt,&t);
if(opt==1){
a[t].push_back(++cnt);
note[cnt]=t;
all++;
}else
if(opt==2){
if(a[t].size()){
all-=a[t].size();
a[t].clear();
}
}else
if(t>--pre){
while(++pre<=t){
if(!a[note[pre]].size())continue;
int now=a[note[pre]].front();
if(now<=t){
a[note[pre]].pop_front();
all--;
}
}
}
cout<<all<<"\n";
}
}
题意:给n个小写字母字符串,使第i个串反转的代价为c[i],可以选择使任意串反转或不反转。求使1~n个字符串按原顺序是字典序升序排列的反转方案的最小代价。
思路:预处理s[i]反转后的字符串ss[i],dp。
设f[i][0]为第i串不反转的最小代价,f[i][1]为第i串反转的最小代价,皆可由f[i-1][0]和f[i-1][1]推出来。(!要初始化f数组为Inf)
结果是min(f[n][0],f[n][1])
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define Inf 100000000000000000
typedef long long ll;
const int N=100007;
string s[N],ss[N];
ll c[N],f[N][2];
int n;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>c[i];
for(int i=1;i<=n;i++){
cin>>s[i];
int l=s[i].length();
for(int j=0;j<l;j++)ss[i]+=s[i][l-j-1];
f[i][0]=f[i][1]=Inf;
}
f[1][0]=0;f[1][1]=c[1];
for(int i=2;i<=n;i++){
if(s[i]>=s[i-1])f[i][0]=f[i-1][0];
if(s[i]>=ss[i-1])f[i][0]=min(f[i][0],f[i-1][1]);
if(ss[i]>=s[i-1])f[i][1]=f[i-1][0]+c[i];
if(ss[i]>=ss[i-1])f[i][1]=min(f[i][1],f[i-1][1]+c[i]);
}
ll ans=min(f[n][0],f[n][1]);
if(ans==Inf)cout<<-1;else cout<<ans;
}
题意:给n<=2000个点,每个点一个坐标(Xi,Yi),-1e9<=Xi,Yi<=1e9,和一个值wi(有负数),找一个矩形,使其框起来的点的wi值和最大。(稀疏点的最大子矩阵和)
思路:离散化+枚举上下边界降维+线段树维护一维的最大连续子段和
线段树维护最大子段和的具体思路:[ 涉及操作1.更改一个值 2.询问[L,R]区间的最大连续子段和(操作2本题没用到) ]
线段树需要维护的是:
_1.[x,y]内的最大子段和 ms
_2.[x,y]的区间和 s
_3.[x,y]内的紧靠左端点的最大子段和 ls
_4.[x,y]内的紧靠右端点的最大子段和 rs
维护方法:
ls有两种情况:
_1.左儿子的ls
_2.左儿子的s+右儿子的ls
同理,rs有两种情况:
_1.右儿子的rs
_2.右儿子的s+左儿子的rs
s有三种情况:
_1.左儿子的s
_2.右儿子的s
_3.左儿子的rs+右儿子的ls
询问方法:
[L,R]区间的最大连续子段和有以下几种情况:
_1.独立的存在于左儿子或右儿子中
_2.左儿子的rs+右儿子的ls
然而如果[L,R] 在线段树中是一个节点(我们单独维护过),那么直接return t[k]这个节点就可以了
#include
using namespace std;
#define Inf 0x7fffffff
typedef long long ll;
const int N=2010;
int T,n,X[N],Y[N];
struct point{
int x,y;
ll w;
}p[N];
struct node{
ll ms,ls,rs,s;
}t[N*4];
void push_up(int k){
int lson=k<<1,rson=k<<1|1;
t[k].s=t[lson].s+t[rson].s;
t[k].ls=max(t[lson].ls,t[lson].s+t[rson].ls);
t[k].rs=max(t[rson].rs,t[rson].s+t[lson].rs);
t[k].ms=max(max(t[lson].ms,t[rson].ms),t[lson].rs+t[rson].ls);
}
void add(int pos,int l,int r,int k,ll val){
if(l==r){
t[k].s=t[k].ls=t[k].rs=t[k].ms+=val;
return;
}
int mid=(l+r)>>1;
if(pos<=mid)add(pos,l,mid,k<<1,val);
else add(pos,mid+1,r,k<<1|1,val);
push_up(k);
}
vector<point>ve[N];
int main(){
cin>>T;
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;i++){//离散化
scanf("%d%d%lld",&p[i].x,&p[i].y,&p[i].w);
X[i]=p[i].x;
Y[i]=p[i].y;
}
sort(X+1,X+1+n);
sort(Y+1,Y+1+n);
int size1=unique(X+1,X+1+n)-X-1;
int size2=unique(Y+1,Y+1+n)-Y-1;
for(int i=1;i<=n;i++){
p[i].x=lower_bound(X+1,X+1+size1,p[i].x)-X;
p[i].y=lower_bound(Y+1,Y+1+size2,p[i].y)-Y;
ve[p[i].y].push_back(p[i]);
}
ll ans=0;
for(int i=1;i<=size2;i++){//枚举上边界
for(int j=1;j<=size1*4;j++) //清空线段树
t[j].s=t[j].ls=t[j].rs=t[j].ms=0;
for(int j=i;j;j--){//枚举下边界
for(auto k:ve[j])add(k.x,1,size1,1,k.w);
ans=max(ans,t[1].ms);
}
}
cout<<ans<<"\n";
for(int i=1;i<=size2;i++)ve[i].clear();
}
}
/*询问[L,R]区间的最大连续子段和
node query(int L,int R,int l,int r,int k){
if(L<=l&&R>=r)return t[k];//完全包含
int mid=(l+r)>>1;
node al,ar,ans;
ans.s=0;
if(R<=mid)ans=query(L,R,l,mid,k<<1);//全部都在左儿子
if(L>mid)ans=query(L,R,mid+1,r,k<<1|1);//全部都在右儿子
if(L<=mid&&R>mid){//询问区间被拆成两部分
al=query(L,R,l,mid,k<<1);
ar=query(L,R,mid+1,r,k<<1|1);
ans.s=al.s+ar.s;
ans.ls=max(al.ls,al.s+ar.ls);
ans.rs=max(ar.rs,ar.s+al.rs);
ans.ms=max(max(al.ms,ar.ms),al.rs+ar.ls);
}
return ans;
}
*/
题意:给出p个矩形范围为覆盖范围,进行q个询问,每次询问一个矩形范围内是否所有点均有覆盖。(p,q<=1e6,坐标范围n*m<=1e7)
思路:二维vector+二维差分+二维前缀和,判断矩形内已覆盖的点数量是否=矩形面积和
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define Inf 0x7fffffff
typedef long long ll;
const int N=100007;
int n,m,p,q;
int main(){
while(~scanf("%d%d",&n,&m)){
vector<vector<int> >d(n+2,vector<int>(m+2)),f(n+2,vector<int>(m+2));
scanf("%d",&p);
int xl,yl,xr,yr;
for(int i=1;i<=p;i++){
scanf("%d%d%d%d",&xl,&yl,&xr,&yr);
++d[xl][yl];
--d[xr+1][yl];
--d[xl][yr+1];
++d[xr+1][yr+1];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
d[i][j]+=d[i][j-1]+d[i-1][j]-d[i-1][j-1];
f[i][j]+=f[i][j-1]+f[i-1][j]-f[i-1][j-1]+(d[i][j]>0?1:0);
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%d%d%d%d",&xl,&yl,&xr,&yr);
int area=f[xr][yr]-f[xl-1][yr]-f[xr][yl-1]+f[xl-1][yl-1];
if(area==(xr-xl+1)*(yr-yl+1))printf("YES\n");
else printf("NO\n");
}
}
}
题意:给n (<=20)个问题,第i个问题的得分为t*a[i]+b[i](t为解答第i个问题的时间,解答每个问题都花费1秒),解答第i个问题之前必须先解决si个问题(给定每个问题的si个前驱pi,1,pi,2,…,pi,si),求一种做题的顺序,以获得最大得分(允许有些题不做,也允许一个题都不做)。
思路:状压dp。因为n<=20,所以可以用20位二进制表示20个问题解决的状态,显然最多有220-1个状态。枚举所有状态,枚举它能由哪些状态+解答第k个题得来,判断这些状态是否满足解答k题的前驱条件(实际这里还需要判断这个状态是否已存在且可行),如果满足,则转移。更新max_ans即可
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=27,M=(1<<20)+7;
int n,f[M],flag[M];
ll a[N],b[N],dp[M],ans;
ll count(int i){//数i的二进制有多少个1
ll cnt=0;
while(i){
cnt++;
i=i&(i-1);
}
return cnt;
}
int main(){
cin>>n;
memset(dp,128,sizeof(dp));
for(int i=1;i<=n;i++){
int s,x;
scanf("%lld%lld%d",&a[i],&b[i],&s);
for(int j=1;j<=s;j++){
scanf("%d",&x);
f[1<<(i-1)]+=1<<(x-1);//标记第i个题的前驱
}
}
ll Inf=dp[0];dp[0]=0;flag[0]=1;
for(int i=1;i<=(1<<n)-1;i++){//枚举状态
ll t=count(i);
for(int d=0;d<=20&&(1<<d)<=i;d++){//枚举题
int now=1<<d;
if((i&now)&&flag[i^now]&&((i^now)&f[now])==f[now]){
if(dp[i]==Inf)dp[i]=dp[i^now]+t*a[d+1]+b[d+1];
else dp[i]=max(dp[i],dp[i^now]+t*a[d+1]+b[d+1]);
flag[i]=1;
}
ans=max(ans,dp[i]);
}
}
cout<<ans;
}
题意:给n个区间每个区间的范围为[ai,bi],让你确定k个整数,使这k个整数在第i个区间[ai,bi]至少有ci个共同的数。(0<=ai<=bi<=50000)
思路:差分约束系统—详解
设dis[i]为[0…i]有几个数被选中,则可得到不等式
①dis[bi]-dis[ai-1]>=ci
②0<=dis[i+1]-dis[i]<=1 后半部分即dis[i]-dis[i+1]>=-1
对dis[bi]-dis[ai-1]>=ci、dis[i+1]-dis[i]>=0、dis[i]-dis[i+1]>=-1建边,SPFA跑最长路。dis[maxb] 即为答案
建边方法:
对于不等式dis[u]-dis[v]>=w即dis[u]>=dis[v]+w,令w为v→u的边,SPFA跑最长路后此式成立(因为SPFA完成之后dis[u]是源点到u的最长距离,dis[v]是源点到v的最长距离,dis[u]显然>=dis[v]+w(v,w)
所以对于dis[ui]-dis[vi]>=wi的不等式组,addedge(vi,ui,wi),跑最长路
___ 对于dis[ui]-dis[vi]<=wi的不等式组,addedge(vi,ui,wi),跑最短路
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define Inf 10000000000000
typedef long long ll;
const int N=50007,M=100007;
int n,mina=N,maxb,vis[N],head[N],cnt;
ll dis[N];
struct EDGE{int v,w,next;}e[M*2];
void addedge(int u,int v,ll w){
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt++;
}
void spfa(int s){//最长路
for(int i=mina;i<=maxb;i++)dis[i]=(i==s?0:-Inf);
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int now=q.front();q.pop();
vis[now]=0;
for(int i=head[now];i!=-1;i=e[i].next)
if(dis[e[i].v]<dis[now]+e[i].w){
dis[e[i].v]=dis[now]+e[i].w;
if(!vis[e[i].v]){
q.push(e[i].v);
vis[e[i].v]=1;
}
}
}
}
int main(){
while(cin>>n){
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
cnt=0;
for(int i=1;i<=n;i++){
int a,b;ll c;
scanf("%d%d%lld",&a,&b,&c);
a++;b++;//由于最小值可能为0,所以防止我的起点为负数
mina=min(mina,a-1);
maxb=max(maxb,b);
addedge(a-1,b,c);
}
for(int i=mina;i<maxb;i++){
addedge(i+1,i,-1);
addedge(i,i+1,0);
}
spfa(mina);
cout<<dis[maxb]<<"\n";
}
}
题意:给出一个k (<=2*t)个点,t (<=100)条边的无向连通图,求s点到e点经过n (<=1e6)条边的最短距离
思路:
思考一下,如果一个矩阵,表示走k条边后,一张图的点与点的最短路径,(a,b)表示从a到b的最短路径,然后我们把它与自己,按照矩阵乘法的格式“相乘”,把其中的乘改为取min,ans.m[i][j] = min(ans.m[i][j],a.m[i][k]+b.m[k][j])【类似于Floyd】。这样得到的是走k+k条边的矩阵,同理,一个走了a条边的矩阵×一个走了b条边的矩阵,得到的是走了a+b条边的矩阵。
所以对点离散化,用矩阵快速幂的方法,修改一下opereator重载的 * 式子求basen即可。
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=207;
int m,s,t,l[N],r[N];
ll w[N],n,Inf;
int U[N],cntu,size;
struct Matrix{
ll m[N][N];
Matrix(){//根据题目进行初始化
memset(m,63,sizeof(m));
}
void clear(){
memset(m,0,sizeof(m));
for(int i=1;i<=size;i++)
m[i][i]=1;
}
void display(){
cout<<"Matrix's begin:"<<endl;
for(int i=1;i<=size;i++)
for(int j=1;j<=size;j++)
if(j<size) cout<<m[i][j]<<" ";
else cout<<m[i][j]<<endl;
cout<<"Matrix's end:"<<endl;
}
friend Matrix operator*(Matrix a,Matrix b){
Matrix ans;
for(int i=1;i<=size;i++) //size*size为矩阵大小
for(int j=1;j<=size;j++)
for(int k=1;k<=size;k++){
if(ans.m[i][j]==Inf)ans.m[i][j]=a.m[i][k]+b.m[k][j];
else ans.m[i][j]=min(ans.m[i][j],a.m[i][k]+b.m[k][j]);
}
return ans;
}
friend Matrix operator^(Matrix base,ll k){
k--;
Matrix ans=base;
while(k){
if(k&1) ans=ans*base;
base=base*base;
k>>=1;
}
return ans;
}
};
int main(){
cin>>n>>m>>s>>t;
for(int i=1;i<=m;i++){
scanf("%lld%d%d",&w[i],&l[i],&r[i]);
U[++cntu]=l[i];
U[++cntu]=r[i];
}
sort(U+1,U+1+cntu);
size=cntu=unique(U+1,U+1+cntu)-U-1;
Matrix base;
Inf=base.m[0][0];
for(int i=1;i<=m;i++){
l[i]=lower_bound(U+1,U+1+cntu,l[i])-U;
r[i]=lower_bound(U+1,U+1+cntu,r[i])-U;
base.m[l[i]][r[i]]=base.m[r[i]][l[i]]=w[i];
}
s=lower_bound(U+1,U+1+cntu,s)-U;
t=lower_bound(U+1,U+1+cntu,t)-U;
base=base^n;
cout<<base.m[s][t];
}