寒假集训day1

4 篇文章 0 订阅
4 篇文章 0 订阅

今天的代码怎么都这么难调啊!!!

T1:游戏
本 题 算 是 个 半 裸 的 S T L 模 板 题 本题算是个半裸的STL模板题 STL
题 意 : 有 w 列 h 行 , 对 于 a ≡ b ( m o d 2 ) 的 点 , 放 一 块 木 板 , 使 ( a , b ) 与 ( a , b + 1 ) 相 连 , 再 每 一 列 上 方 放 一 个 球 垂 直 下 落 , 遇 到 木 板 沿 着 木 板 往 相 邻 位 置 走 , 给 出 n 个 减 少 的 木 板 , 求 每 个 点 的 最 终 位 置 题意:有w列h行,对于a\equiv b\pmod{2}的点,放一块木板,使(a,b)与(a,b+1)相连,再每一列上方放一个球垂直下落,遇到木板沿着木板往相邻位置走,给出n个减少的木板,求每个点的最终位置 whab(mod2)使(a,b)ab+1沿n
先 考 虑 没 有 减 少 木 板 的 情 况 , 观 察 第 一 列 发 现 , 由 于 相 邻 两 球 遇 到 同 一 块 木 板 时 位 置 交 换 , 所 以 可 以 对 每 一 列 分 别 考 虑 先考虑没有减少木板的情况,观察第一列发现,由于相邻两球遇到同一块木板时位置交换,所以可以对每一列分别考虑
对 于 每 一 列 , 我 们 发 现 : 对于每一列,我们发现: :
1. 若 为 奇 数 列 , 直 接 将 奇 偶 相 邻 交 换 1. 若为奇数列,直接将奇偶相邻交换 1. 2. 若 为 偶 数 列 , 收 尾 不 变 , 中 间 的 相 邻 交 换 2. 若为偶数列,收尾不变,中间的相邻交换 2.
考 虑 拆 掉 一 些 木 板 , 则 表 示 这 一 行 的 这 一 列 不 会 被 交 换 考虑拆掉一些木板,则表示这一行的这一列不会被交换 ,
所 以 我 们 可 以 用 两 个 双 端 队 列 维 护 每 个 球 最 终 位 置 , 最 后 在 记 录 答 案 即 可 所以我们可以用两个双端队列维护每个球最终位置,最后在记录答案即可
注 意 点 : 注意点:

  1. deque和stack的运用,即下标不一定从1开始,应用迭代器iterator
  2. 木板为奇数列或者偶数列的时候,操作是不同的,因为如果是偶数列的话,与右边奇数列交换,此时奇数的序数(即在原序列中为第几个奇数)比偶数的序数大1,而如果是偶数的话,两个的序数是相等的
  3. 迭代器下标从0开始,end()应该是开区间
  4. 题目问的不是每个落点的最终球,而是每个球的最终落点,所以最后还需要计算一次
  5. 因为交换时为了方便,要再记录一个ans1,ans2(当然,大佬如果可以不用就忽略),ans1表示fir为奇数队列还是偶数队列,ans2表示sec为奇数队列还是偶数队列,然后在后面操作的时候要分两种情况讨论
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+5;
int h,w,n;
vector <int> ball[N];
deque <int> fir,sec;
int ans1=1,ans2=2,ans[N];
int main(){
	scanf("%d%d%d",&h,&w,&n);
	for(int i=1;i<=n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		ball[x].push_back(y);
	}
	for(int i=1;i<=w;i++){
		if(i&1){
			fir.push_back(i);
		}
		else{
			sec.push_back(i);
		}
	}
	for(int i=1;i<=h;i++){
		if(i&1){//对奇偶行分别判断
			swap(ans1,ans2);
			vector <int>::iterator it;
			for(it=ball[i].begin();it<ball[i].end();it++){//
				int v=*it;
				if(v%2==0){//对删掉的木板是奇数列还是偶数列讨论
				int tmp=v/2; 
			    if(ans1==1)//对奇偶队列产生的影响讨论
				{
				int tmpf=fir[tmp],tmps=sec[tmp-1];
			    fir[tmp]=tmps,sec[tmp-1]=tmpf;//
			    }
			    if(ans2==1)//对奇偶队列产生的影响讨论
				{
				int tmpf=fir[tmp-1],tmps=sec[tmp];
			    fir[tmp-1]=tmps,sec[tmp]=tmpf;//
			    }
				}
				if(v%2==1){//对删掉的木板是奇数列还是偶数列讨论
				int tmp=v/2+1; 
			    if(ans1==1)//对奇偶队列产生的影响讨论
				{
				int tmpf=fir[tmp-1],tmps=sec[tmp-1];
			    fir[tmp-1]=tmps,sec[tmp-1]=tmpf;//
			    }
			    if(ans2==1)//对奇偶队列产生的影响讨论
				{
				int tmpf=fir[tmp-1],tmps=sec[tmp-1];
			    fir[tmp-1]=tmps,sec[tmp-1]=tmpf;//
			    }
				}
		     }
		}
		if((!(i&1))){//对奇偶行分别判断
		    if(ans1==1){//对奇偶队列产生的影响讨论
			int tmp1=fir.front(),tmp2=sec.back();//
			fir.pop_front(),fir.push_back(tmp2);
		    sec.pop_back(),sec.push_front(tmp1);
		    }
		    if(ans2==1){//对奇偶队列产生的影响讨论
		    int tmp1=sec.front(),tmp2=fir.back();//
		    sec.pop_front(),sec.push_back(tmp2);
		    fir.pop_back(),fir.push_front(tmp1);
		    }
		    swap(ans1,ans2);
		    vector <int>::iterator it;
		    for(it=ball[i].begin();it<ball[i].end();it++){
		    	int v=*it;
		    	if(v%2==0){//对删掉的木板是奇数列还是偶数列讨论
				int tmp=v/2; 
			    if(ans1==1)
				{
				int tmpf=fir[tmp],tmps=sec[tmp-1];
			    fir[tmp]=tmps,sec[tmp-1]=tmpf;//
			    }
			    if(ans2==1)//对奇偶队列产生的影响讨论
				{
				int tmpf=fir[tmp-1],tmps=sec[tmp];
			    fir[tmp-1]=tmps,sec[tmp]=tmpf;//
			    }
				}
				if(v%2==1){//对删掉的木板是奇数列还是偶数列讨论
				int tmp=v/2+1; 
			    if(ans1==1)
				{
				int tmpf=fir[tmp-1],tmps=sec[tmp-1];
			    fir[tmp-1]=tmps,sec[tmp-1]=tmpf;//
			    }
			    if(ans2==1)//对奇偶队列产生的影响讨论
				{
				int tmpf=fir[tmp-1],tmps=sec[tmp-1];
			    fir[tmp-1]=tmps,sec[tmp-1]=tmpf;//
			    }
				}
		    }
		}
	}
	int tnt=0;
	if(ans1==1){
		while(!sec.empty()){//
			int font=fir.front(),sont=sec.front();
			fir.pop_front(),sec.pop_front();
			ans[font]=++tnt;ans[sont]=++tnt;
	    }
	}
	if(ans1==2){
		while(!fir.empty()){//
			int font=fir.front(),sont=sec.front();
			fir.pop_front(),sec.pop_front();
			ans[sont]=++tnt,ans[font]=++tnt;
 	    }
	}
	for(int i=1;i<=w;i++){//
		printf("%d\n",ans[i]);
	}
}

T2:画画
首 先 观 察 题 目 , 发 现 走 过 去 再 走 回 来 的 操 作 像 极 了 N O I P 某 年 的 d p 题 首先观察题目,发现走过去再走回来的操作像极了NOIP某年的dp题 NOIPdp
所 以 运 用 套 路 走 两 次 所以运用套路走两次
我 开 始 的 想 法 是 因 为 只 会 往 右 走 或 往 下 走 , 所 以 能 走 就 一 定 走 完 , 后 来 发 现 走 两 次 , 就 以 为 不 行 , 结 果 发 现 没 法 判 断 只 有 1 条 路 径 的 情 况 我开始的想法是因为只会往右走或往下走,所以能走就一定走完,后来发现走两次,就以为不行,结果发现没法判断只有1条路径的情况 1
正 解 : 正解:
对 于 第 一 条 路 径 : 能 往 下 走 就 走 , 不 能 走 就 往 右 走 对于第一条路径:能往下走就走,不能走就往右走
对 于 第 二 条 路 径 , 能 往 右 走 就 往 右 走 , 不 能 走 就 往 下 对于第二条路径,能往右走就往右走,不能走就往下
对 于 重 合 的 点 乘 2 , 因 为 来 到 重 合 点 的 路 径 对 于 第 一 条 和 第 二 条 可 以 互 换 对于重合的点乘2,因为来到重合点的路径对于第一条和第二条可以互换 2
注 意 点 : 注意点:

  1. 如果遇到连续的重合点只用乘1次2,因为对于连续的重合点,重合点之间的路径是唯一的,互换不会产生新的方案
  2. 收尾两个点的路径互换只能算一次,如果交换两次的话,和原来的方案是相同的(我代码中算的是尾点)
  3. 起点开始的连续路径不能计入答案(如果和我一样计算尾点的话)
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e5+5;//
int a[N],b[N],suma[N],sumb[N];
int n;
ll ans=1,mod=1e9+7;//
bool flag=false,flag2=false;//
void dfs(int x,int y,int xx,int yy,bool ff){//ff用来判断连续的重合点
	if(suma[x]>a[x]||sumb[y]>b[y]||suma[xx]>a[xx]||sumb[yy]>b[yy]) {if(x==xx&&y==yy) ans/=2;return;};
	if(x!=xx||y!=yy){
		flag=true;
	}
	if(x==n&&y==n&&xx==n&&yy==n){
		flag2=true;
		return;
	}
	if(suma[x]+1<=a[x]&&sumb[yy]+1<=b[yy]&&y+1<=n&&xx+1<=n){
		suma[x]++,sumb[yy]++;
		bool f=false;
		if(x!=xx+1||y!=yy+1)sumb[y+1]++,suma[xx+1]++;
		if(x==xx+1&&y+1==yy) {if(!ff) ans=ans*2%mod;f=true;};
		dfs(x,y+1,xx+1,yy,f);
		suma[x]--,sumb[yy]--;
		if(x!=xx+1||y!=yy+1)sumb[y+1]--,suma[xx+1]--;
	}
	if(suma[x]==a[x]&&x+1<=n&&xx+1<=n&&sumb[yy]+1<=b[yy]){
		suma[x+1]++,sumb[yy]++;
		bool f=false;
		if(x+1!=xx+1||y!=yy)suma[xx+1]++,sumb[y]++;
		if(x+1==xx+1&&y==yy){if(!ff) ans=ans*2%mod;f=true;}
		dfs(x+1,y,xx+1,yy,f);
		suma[x+1]--,sumb[yy]--;
		if(x+1!=xx+1||y!=yy)suma[xx+1]--,sumb[y]--;
	}
	if(sumb[yy]==b[yy]&&yy+1<=n&&y+1<=n&&suma[x]+1<=a[x]){
		suma[x]++,sumb[yy+1]++;
		bool f=false;
		if(x!=xx||y+1!=yy+1)suma[xx]++,sumb[y+1]++;
		if(x==xx&&y+1==yy+1){if(!ff) ans=ans*2%mod;f=true;}
		dfs(x,y+1,xx,yy+1,f);
		suma[x]--,sumb[yy+1]--;
		if(x!=xx||y+1!=yy+1)suma[xx]--,sumb[y+1]--;
	}
	if(sumb[yy]==b[yy]&&suma[x]==a[x]&&x+1<=n&&yy+1<=n){
		sumb[y]++;suma[xx]++;
		bool f=false;
		if(x+1!=xx||y!=yy+1)suma[x+1]++,sumb[yy+1]++;
		if(x+1==xx&&y==yy+1){if(!ff) ans=ans*2%mod;f=true;}
		dfs(x+1,y,xx,yy+1,f);
		sumb[y]--;suma[xx]--;
		if(x+1!=xx||y!=yy+1)suma[x+1]--,sumb[yy+1]--;
	}
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
	flag2=false;flag=false;
    memset(suma,0,sizeof(suma));
    memset(sumb,0,sizeof(sumb));
	scanf("%d",&n);	ans=1;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&b[i]);
	}
	suma[1]++,sumb[1]++;
	dfs(1,1,1,1,1);
	if(!flag2){
		puts("0");
		continue;
	}
	if(!flag){
		puts("1");
		continue;
	}	
	printf("%lld\n",ans);
    }
}

T3:
洛古地址
( 洛 古 上 的 数 据 比 较 小 , 需 要 调 整 ) (洛古上的数据比较小,需要调整)
实 在 是 看 不 懂 写 滚 动 数 组 的 是 怎 么 过 的 , 当 多 了 一 位 j 时 , 不 知 道 为 什 么 j − 1 的 决 策 点 可 以 直 接 给 j 实在是看不懂写滚动数组的是怎么过的,当多了一位j时,不知道为什么j-1的决策点可以直接给j jj1j
本 题 解 比 较 好 理 解 , 但 空 间 较 大 本题解比较好理解,但空间较大
d p [ i ] [ j ] 表 示 取 [ 1 , i ] 只 猫 , 用 j 个 管 理 员 的 最 小 等 待 时 间 dp[i][j]表示取[1,i]只猫,用j个管理员的最小等待时间 dp[i][j][1,i]j
d p [ i ] [ j ] = d p [ k ] [ j − 1 ] + ( j − k ) ∗ T [ j ] − ( s u m [ j ] − s u m [ k ] ) dp[i][j]=dp[k][j-1]+(j-k)*T[j]-(sum[j]-sum[k]) dp[i][j]=dp[k][j1]+(jk)T[j](sum[j]sum[k])
移 一 下 项 , 发 现 d p [ k ] [ j − 1 ] + s u m [ k ] = T [ j ] ∗ k + d p [ i ] [ j ] − j ∗ T [ j ] + s u m [ j ] 移一下项,发现dp[k][j-1]+sum[k]=T[j]*k+dp[i][j]-j*T[j]+sum[j] dp[k][j1]+sum[k]=T[j]k+dp[i][j]jT[j]+sum[j]
因 为 T [ j ] 单 调 递 增 , 所 以 我 们 可 以 维 护 一 个 凸 包 , 但 由 于 是 二 维 , 所 以 要 维 护 多 个 凸 包 , 每 次 判 断 队 首 的 斜 率 是 否 小 于 T [ j ] , 是 就 弹 出 队 列 , 直 到 大 于 T [ j ] ( 因 为 T [ j ] 单 调 递 增 , 所 以 后 面 也 不 会 再 用 到 弹 出 的 元 素 ) 因为T[j]单调递增,所以我们可以维护一个凸包,但由于是二维,所以要维护多个凸包,每次判断队首的斜率是否小于T[j],是就弹出队列,直到大于T[j](因为T[j]单调递增,所以后面也不会再用到弹出的元素) T[j]T[j]T[j]T[j]
接 下 来 取 出 队 首 转 移 即 可 接下来取出队首转移即可
(实在不明白为什么多一维之后决策点还是能直接用!!!)

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,m,p;
int dis[N],T[N];
typedef long long ll;
ll dp[50005][1003],q[50005][1003],hd[50005],tl[50005],sum[N];
inline double slope(int tt,int a,int b){
	return double(dp[a][tt]+sum[a]-dp[b][tt]-sum[b])/(a-b);
}
int main(){
	scanf("%d%d%d",&n,&m,&p);
	dis[1]=0;
	for(int i=2;i<=n;i++){
		int d;
		scanf("%d",&d);
		dis[i]=dis[i-1]+d;
	}
	for(int i=1;i<=m;i++){
		int h,t;
		scanf("%d%d",&h,&t);
		T[i]=t-dis[h];
	}
	sort(T+1,T+m+1);
	for(int i=1;i<=m;i++){
		sum[i]=sum[i-1]+T[i];
	}
	for(int j=0;j<=m;j++){
		hd[j]=1,tl[j]=0;
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=p;j++){
			while(hd[j-1]<tl[j-1]&&slope(j-1,q[j-1][hd[j-1]],q[j-1][hd[j-1]+1])<=T[i]) hd[j-1]++;//
			int nxt=q[j-1][hd[j-1]];
			dp[i][j]=dp[nxt][j-1]+(i-nxt)*T[i]-(sum[i]-sum[nxt]);
			while(hd[j]<tl[j]&&slope(j,q[j][tl[j]],q[j][tl[j]-1])>slope(j,q[j][tl[j]],i)) tl[j]--;
			q[j][++tl[j]]=i;
		}
	}
	printf("%lld",dp[m][p]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值