2011D1T2客栈
Task
N个点,两个属性c,v表示颜色和花费,求区间[ l, r ]满足l,r颜色相同,且这个区间最小值<=p的个数
N<=2e5,c<=50,p<=100
Solution
如果枚举两个端点,复杂度是O(n^2),用前缀和询问这个区间是否有<=p的数,来判断这个区间是否可行。
区间问题常见的优化方法:枚举右端点,找左端点。
因为颜色的个数很小,因此可以按颜色分类,放入vector里。找到每个点前面最近的<=p的点,那么位置在p之前的点都可以成为左端点。用STL里的lower_bound函数处理很方便。
其实,这题还可以排列组合来做,复杂度O(nk)。个数=总区间数-不可行的区间数。
正难则反,只要求出不可能的区间数,v<=p的点会把这个区间分成一段一段的,那么单独一段里的就是不含v<=p的点,这一段内部的区间都是不可行的的区间
const int M=53;
vector<int>s[M];
inline int query(int c,int x){return upper_bound(s[c].begin(),s[c].end(),x)-s[c].begin();}
int main(){
int i,j,k,c,val,pos=-1,tot,p,n,res;
ll ans=0;
rd(n);rd(tot);rd(p);
rep(i,1,n){
rd(c);rd(val);
if(val<=p)pos=i;
s[c].pb(i);
if(pos==-1)continue;
if(pos==i)ans+=query(c,pos-1);
else ans+=query(c,pos);
}
cout<<ans<<endl;
return 0;
}
2011D1T3 游戏
Task
如果存在,在7*5的界面,输出k操作后,方格都被消除的字典序最小的方案。
操作规则:
① 每次选择一个方格与它相邻的左或右方格交换,当前不能是空方格,左右可以是空方格
② 操作以后,方格会先落下来,再消除
③ 任意行列有超过3个同颜色的方格会被消除
颜色少于10种,操作数k<=5,字典序的关键字次序是 x,y,左比右小
对于30%数据,初始方格都在棋盘最下面一行
Solution
由题意和k<=5确定这是一道暴搜题。类似做过的题目有popstar 消除星星
剪枝:
① 交换的两个颜色相同,则不交换
② 当前存在某个颜色少于3个,这个状态是一定不可行的。
暴搜题只要确定好思路,定下框架,就一定能成功。
把消除,掉落,检查颜色个数都用函数来处理。
下面是我在搜索过程中犯的错误
① 题中的x表示列,y表示行,因为用的不习惯,把它转换了,但是最后忘记换回去了。
② 掉落的Fall函数中,一列列处理去除中间空的部分,但是如果遇到空的,不能break,它上面可能还有实方格。
/*
x∈[1,n],y∈[1,m]
相反输出
能在函数里改变结构体的值: 全局变量 传入数组 &node
*/
int ry[]={1,-1};
int n=7,m=5,tot,maxcol,yzq;
bool vis[8][6],flag;
int ans[7][5];//记录答案
bool mark[5];
struct node{
int a[8][6];
}A;
inline void input(){
int i,j,k,num=0;
rd(tot);
rep(j,1,m){
num=0;
while(1){
rd(k);
if(k==0)break;
A.a[++num][j]=k;
}
}
}
inline bool Empty(int a[8][6]){//检查第一层
int i,j;
rep(j,1,m)if(a[1][j]>0)return 0;
return 1;
}
inline bool Nosolution(int a[8][6]){//剪枝:存在某一个颜色个数<=2 则一定不可能
int i,j,cnt[11];
memset(cnt,0,sizeof(cnt));
rep(j,1,m)
rep(i,1,n){
if(a[i][j]==0)break;
cnt[a[i][j]]++;
}
rep(i,1,maxcol)if(cnt[i]>0&&cnt[i]<=2)return 1;
return 0;
}
inline bool Wipe(int a[8][6]){//消除行列超过3个连续的
bool f=0;//标记存在消除
int i,j,k,p;
memset(vis,0,sizeof(vis));//标记这个点是否被消除
rep(i,1,n)//处理行连续
for(j=1;j<=m;j=k){
k=j;
while(k<=m&&a[i][k]==a[i][j])k++;//注意下标越界
if(a[i][j]==0)continue;
if(k-j>=3){//存在至少3个连续的
rep(p,j,k-1)vis[i][p]=1;
f=1;
}
}
rep(j,1,m)//处理列连续
for(i=1;i<=n;i=k){
k=i;
while(k<=n&&a[k][j]==a[i][j])k++;//注意下标越界
if(a[i][j]==0)continue;
if(k-i>=3){
f=1;
rep(p,i,k-1)vis[p][j]=1;
}
}
return f;
}
inline bool Fall(int a[8][6]){
int i,j,num;
int b[8][6];
memset(b,0,sizeof(b));//初始化数组
rep(j,1,m){//一列列处理,除去中间空的部分 如果遇到空的,上面可能还有方格
num=0;
rep(i,1,n){
if(a[i][j]==0||vis[i][j]==1)continue;
b[++num][j]=a[i][j];
}
}
rep(i,1,n)rep(j,1,m)a[i][j]=b[i][j];//赋值回去
}
inline void print(node A,int c){
int i,j;
per(i,n,1){
rep(j,1,m)
printf("%d ",A.a[i][j]);
puts("");
}
putchar('\n');
}
inline void dfs(node A,int c){//剪枝:避免重复
if(c==0){//递归终点 已经用完了所有的步数
if(Empty(A.a))flag=1;
return;
}
if(Nosolution(A.a))return;
int i,j,k,nj;
rep(j,1,m){//交换相邻两个 x,y次序和题目相反
if(flag)break;
rep(i,1,n){
if(flag)break;
if(!A.a[i][j])break;//空方块不能交换
ans[c][0]=i,ans[c][1]=j;
rep(k,0,1){//确定方向
if(flag)break;
nj=j+ry[k];
if(nj>m||nj<1)continue;//数组越界
if(A.a[i][j]==A.a[i][nj])continue;//剪枝:相邻两个颜色相同,不交换
if(A.a[i][nj]>0&&k==1)continue;//如果左边不是空方格,那么可以由左边的向右交换
ans[c][2]=ry[k];
node B=A;
swap(B.a[i][j],B.a[i][nj]);
Fall(B.a);//先掉下来一个
while(Wipe(B.a))Fall(B.a);//当可以消除的时候,可能会一直落下来
dfs(B,c-1);
}
}
}
}
inline void solve(){
dfs(A,tot);
int i;
if(!flag)puts("-1");
else per(i,tot,1)printf("%d %d %d\n",ans[i][1]-1,ans[i][0]-1,ans[i][2]);
}
int main(){
input();
solve();
return 0;
/*
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 3 4
0 1 2 4 3
0 2 3 1 4
0 1 2 3 4
*/
}
2011D2T2
监质员
Task
N个点,两个属性w,v表示重量和价值。标准值为S。
流程是
请确定一个参数W,使检验总和与标准值之差的绝对值最小,输出最小绝对值
Solution
发现W有单调性。
如果W越小,每个区间符合条件个数越多,价值和越大。
因此可以二分处理。
如果当前总和比S小,W变小,反之变大,一直逼近S,得到最小的绝对值。
const int M=2e5+5;
int w[M],v[M],L[M],R[M],cnt[M];
int n,m;
ll S,sum[M],ans=1e18;
inline void input(){
rd(n);rd(m);rd(S);
rep(i,1,n)rd(w[i]),rd(v[i]);
rep(i,1,m)rd(L[i]),rd(R[i]);
}
inline bool check(int x){
int i,j,k,a=0;
ll b=0,res=0;
rep(i,1,n){
cnt[i]=cnt[i-1];
sum[i]=sum[i-1];
if(w[i]>=x){
cnt[i]++;
sum[i]+=v[i];
}
}
rep(i,1,m)res+=1ll*(cnt[R[i]]-cnt[L[i]-1])*(sum[R[i]]-sum[L[i]-1]);
MIN(ans,abs(res-S));
return res>=S;
}
inline void solve(){
int i,j,k,l,r,mid,res;
l=0,r=1e7;
while(l<=r){
mid=l+r>>1;
if(check(mid)){l=mid+1;res=mid;}
else r=mid-1;
}
i=check(res+1);
cout<<ans<<endl;
}
int main(){
input();
solve();
return 0;
}
2011D2T3
观光
Task
n个点在一条直线上,任意两点距离为di。m个人,在Ti时刻到Ai去Bi( Ai小于Bi )。公交必须等每个人i点等待的人都到达后,才能从i出发。旅行时间为结束时刻-初始时刻。K个加速器,可以让相邻两点间的d-1,但是不能减为负数。
求最小的总旅行时间。
N<=1e3,m<=1e4,k<=1e5,k<=100,T<=1e5
Solution
定义st[i]为从这个点出发的时间,是已经确定的。
arr[i]为最早到达i点的时间。
如果修改了区间[ i, i+1 ]的d,那么影响的是之后一段连续区间的arr值,直到有一个st值比较大,之后的arr值都不会改变了。
因此我们可以计算,修改每一小段区间的d值,可以影响到arr的一段区间[ l , r ],那么此时在[ l ,r ]区间下车的人结束的时间都会少1.
贪心地每次去选影响区间内下车人数最多的点,根据相邻交换法,可以证明是正确的。
const int M=1005;
int st[M],arr[M],far[M],sum[M],d[M];
/*
sum(i) 在[1,i]下车的人数
far(i) 第一个不能更新arrive的点
st(i) i点最早的开始时刻
arr(i)=max( st(i-1),arr(i-1) )+d(i-1) 到达i的时间
d(i)-1时 更新的范围是( i+1,far(i) )
*/
int n,m,tot,ans;
inline void input(){
int i,j,k,t,a,b;
rd(n);rd(m);rd(tot);
rep(i,1,n-1)rd(d[i]);
rep(i,1,m){
rd(t);rd(a);rd(b);
MAX(st[a],t);
sum[b]++;
ans-=t;
}
}
inline void init(){//不用加压器的情况
int i,j,k;
rep(i,2,n){
arr[i]=max(st[i-1],arr[i-1])+d[i-1];
ans+=sum[i]*arr[i];//以当前点为结束时刻的总时间
}
rep(i,1,n+1)sum[i]+=sum[i-1];
}
inline int cal(){//计算最远到达的点 影响最优的点
int i,j,x=0;
far[n]=n+1;
per(i,n-1,1){
if(arr[i+1]>st[i+1])far[i]=far[i+1];//下一个点可更新
else far[i]=i+1;//不能更新
if(d[i]>0&&sum[far[i]]-sum[i]>sum[far[x]]-sum[x])x=i;//影响最大的点
}
d[x]--;
rep(i,x+1,far[x])arr[i]--;
return x;
}
inline void solve(){
int x;
while(tot--){//贪心 每次找最优的
x=cal();
ans-=sum[far[x]]-sum[x];
}
printf("%d\n",ans);
}
int main(){
input();
init();
solve();
return 0;
}