ZJNU 2021-07-16 个人排位赛5 部分题解
[广告位招租]
思路看不懂?代码来凑!(狗头保命)代码里也放了注释了
A - Minimizing Edges
题意
防AK,不读了 😦
B - No Time to Dry
题意
Bessie想涂一面墙,初始时墙是无色的
Bessie每次涂色只能用暗色(编号大的)覆盖亮色(编号小的)
有\(Q\)次询问,每次询问给定一个区间\([l,r]\)
如果只对\([l,r]\)区间进行涂色,其余区间不涂色,那么最少的涂色次数为多少?
标签
Monotonous Stack
Binary Index Tree
思路
第二场个人赛[H - No Time to Paint]的另外一个版本,这次涂的是区间内部
由于区间左右侧是不涂色的,所以只能单独处理每个询问表示的小区间
在线做法(适用于只有一个询问)是单调栈的基础题,但本题的询问量用单调栈模拟肯定会超时
但我们想到,单调栈做法的原理实际上就是:
- 如果两个值相同的点\(a,b\ (v_a=v_b,\ a\lt b)\)之间都是大于它们的值的点\(c\ (v_c\ge v_a=v_b,\ a\lt c\lt b)\),那么如果这两个点都被用到,就可以只涂一次使得两个点都涂上正确的颜色
所以考虑离线做法,处理顺序是从左到右,所以先把所有询问存起来,按右区间从小到大排序
假如我们处理到了位置\(p\),则只相应右区间为\(p\)的询问即可
按顺序处理,像只做一次询问那样维护一个单调栈:
- 假设当前处理到了位置\(i\),表示的值是\(ar_i\)
- 如果栈顶表示的值比\(ar_i\)大,那么就将其弹出栈,直到栈顶的值小于等于\(ar_i\)(维护单调性)
- 如果栈顶的值\(ar_s\)等于\(ar_i\),说明如果位置\(s\)被包含在右区间\(\ge i\)的询问中,那么\(s\)与\(i\)可以仅通过一次涂色得到(中间的值根据单调栈处理,肯定都是大于等于它们的)
- 所以将\(s\)的位置标记一个\(1\),表示如果用到这个位置就减去一次涂色次数
- 更新栈顶位置\(s\rightarrow i\)
- 最后,对于右区间\(=i\)的询问,答案就是区间长度减去区间中被标记的次数
被标记的次数可以用树状数组/线段树来求出
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int MAXN=200010;
struct BIT
{
int a[MAXN];
void update(int p,int v){for(;p<MAXN;p+=p&-p)a[p]+=v;}
int query(int p){int r=0;for(;p;p-=p&-p)r+=a[p];return r;}
int query(int l,int r){return query(r)-query(l-1);}
}tree;
struct query
{
int l,r,len,id;
bool operator < (const query& a) const
{
// 主要按照右区间从小到大排序
if(r!=a.r)
return r<a.r;
return l<a.l;
}
}qr[MAXN];
int ar[MAXN];
int ans[MAXN];
void solve()
{
int n,q;
cin>>n>>q;
rep(i,1,n)
cin>>ar[i];
rep(i,1,q)
{
cin>>qr[i].l>>qr[i].r;
// 记录区间长度及原询问位置
qr[i].len=qr[i].r-qr[i].l+1;
qr[i].id=i;
}
sort(qr+1,qr+q+1);
int pos=1;
stack<int> sk;
rep(i,1,n)
{
// 单调栈,将大于当前值的弹出栈
while(!sk.empty()&&ar[sk.top()]>ar[i])
sk.pop();
// 如果栈顶的值与当前值相同
// 说明如果栈顶的值表示的位置被引用到
// 可以直接涂色涂到当前位置
// 所以在栈顶位置+1,更新栈顶
if(!sk.empty()&&ar[sk.top()]==ar[i])
{
tree.update(sk.top(),1);
sk.pop();
}
sk.push(i);
// 处理右区间与当前位置相同的询问
while(pos<=q&&qr[pos].r==i)
{
ans[qr[pos].id]=qr[pos].len-tree.query(qr[pos].l,qr[pos].r);
pos++;
}
}
rep(i,1,q)
cout<<ans[i]<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
C - Just Green Enough
题意
\(N\times N\)的网格,每个格子都有一个\(1\)到\(200\)之间的值
定义一个子矩阵是”足够绿但不是特别绿“的,当且仅当子矩阵中的最小值严格为\(100\)
问存在多少个这样的子矩阵
标签
State Compression
Implementation & Preprocessing
Counting
思路
要求找出最小值严格\(=100\)的子矩阵数量
实际上可以找出最小值\(\ge 100\)的数量,再找出最小值\(\gt 100\)的数量
两者相减,便能得到答案
如果我们要找最小值\(\ge x\)的数量,实际上可以压缩一下状态方便处理,将\(\ge x\)的值标记为\(1\),否则标记为\(0\)
然后需要处理的就是全为\(1\)的子矩阵数量,显然\(O(n^4)\)枚举左上角与右下角的点是行不通的,所以考虑降一级复杂度
先还是\(O(n^2)\)枚举子矩阵的左上角\((i,j)\)
如果我们想知道行高为\(1\)的子矩阵有多少个:
- 那也就是从\((i,j)\)向右侧寻找,要保证子矩阵内的值全为\(1\),所以一旦遇到\(0\)就结束寻找
- 假设\((i,j)\)右侧第一个\(0\)的坐标为\((i,t_1)\),那么满足条件的子矩阵个数就是\(t_1-j\)
那么,假如行高\(+1\),如果想知道有多少个行高为\(2\)的子矩阵:
- 这需要在保证第一行全为\(1\)的基础上,在第二行中去找
- 假设\((i+1,j)\)右侧第一个\(0\)的坐标是\((i+1,t_2)\)
- 如果\(t_1\ge t_2\),那么满足条件数量的将受到第二行的影响,个数为\(t_2-j\)
- 否则,即使第二行连续\(1\)的数量多,但第一行还是得满足全为\(1\)的条件,所以个数还是\(t_1-j\)
综上,我们可以在枚举出\((i,j)\)后,按行递增找出右侧第一个\(0\)的\(y\)坐标\(t_k\),按行数增加让\(t_k\)每次都取小,答案每次增加\(t_k-j\)
但是枚举行高有一个\(O(n)\),还需要找右侧第一个\(0\),也有一个\(O(n)\)
所以这里可以先\(O(n^2)\)预处理一个数组\(rightzero[i][j]\),表示\((i,j)\)右侧第一个\(0\)的\(y\)坐标
最终时间复杂度为\(O(n^3)\)
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int n;
int a[505][505];
int b[505][505];
int c[505][505];
int rightzero[505][505];
ll cal(int ar[505][505])
{
rep(i,1,n)
{
int t=n+1;
// 自右向左处理,记录右侧第一个0的y坐标
per(j,n,1)
{
if(ar[i][j]==0)
t=j;
rightzero[i][j]=t;
}
}
ll r=0;
rep(i,1,n)
rep(j,1,n) //(i,j)作为子矩阵左上角
{
if(ar[i][j]==0)
continue;
// 枚举子矩阵的右下角(k,t-1)
// t也就是右侧第一个0的位置
int t=rightzero[i][j];
// 说明j到t-1都是1,算入答案
r+=t-j;
rep(k,i+1,n)
{
//注意取小,否则子矩阵中可能包括0
t=min(t,rightzero[k][j]);
r+=t-j;
}
}
return r;
}
void solve()
{
cin>>n;
rep(i,1,n)
rep(j,1,n)
cin>>a[i][j];
rep(i,1,n)
rep(j,1,n)
{
b[i][j]=(a[i][j]>=100);
c[i][j]=(a[i][j]>100);
}
// 最小值大于等于100 - 最小值大于100 = 最小值等于100
cout<<cal(b)-cal(c)<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
D - Year of the Cow II
题意
Bessie有\(N\)个祖先,分别生活在\(a_i\)年前(当前是牛年)
Bessie想通过时间传送门去访问每个祖先,最后返回当下
时间传送门只在牛年的某一天开放,且也只能传送到以前的某个牛年
所有的祖先都不生活在牛年
当某个牛年到来时,Bessie可以决定是否使用传送门
如果使用了某个传送门到某个牛年,Bessie必须度过这\(12\)年,然后等待下一个牛年再使用传送门
Bessie最多可以使用\(K\)次传送门
问Bessie至少要花多少年才能拜访完所有祖先并返回当下
标签
Sorting
思路
先将以前的时间分块,每\(12\)年分为一块,方便计算
由于”如果使用了某个传送门到某个牛年,Bessie必须度过这\(12\)年,然后等待下一个牛年再使用传送门“
所以每块一旦用到就要用完,也就是这\(12\)年必须度过
计算出\(N\)位祖先分别处于哪一块,排序后将重复删去(也可以不删,删了方便处理)
首先我们考虑传送门只能用一次的情况(\(K=1\)),明显就是传送到最久远的祖先所生活的那一时间块的牛年,然后按时间顺序访问所有祖先,最终回到现代
所以我们使用传送门时,可以贪心地先在时间间隔大的两块时间块中用掉,这样对总时间节约的贡献值最大
也就是说,可以按照处理出的时间块两两之间的间隔进行排序,然后优先在大的间隔里使用传送门
假设在\(K=1\)时,时间为\(h\)(只能传送一次),最终答案应该就是\(h\)减去最大的\(K-1\)段时间间隔
注意,如果两位祖先所在的分块是相邻的,则这\(24\)年间不需要使用传送门,按顺序度过即可
所以时间间隔的计算应该是\(a_{i+1}-a_i-1\),详情间代码部分
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int n,k;
int a[66666],b[66666];
void solve()
{
cin>>n>>k;
rep(i,1,n)
{
cin>>a[i];
a[i]=(a[i]+11)/12; //计算时间块,即a[i]/12向上取整
}
sort(a+1,a+n+1);
n=unique(a+1,a+n+1)-(a+1); //判重
a[0]=0; //注意还需要返回当下,时间块定为0
//rep(i,1,n)cout<<a[i]<<'\n';
rep(i,1,n)
b[i]=a[i]-a[i-1]-1; //计算时间间隔
sort(b+1,b+n+1);
int ans=a[n];
rep(i,n-k+2,n) //取大的k-1块减去
ans-=b[i];
cout<<ans*12<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
E - Stone Game
题意
有\(N\)堆石子,每堆\(a_i\ (1\le a_i\le 10^6)\)粒
Bessie和Elsie轮流取石子,Bessie先手
- 第一步,Bessie选择一个数字\(s_1\),然后选一堆不少于\(s_1\)的石子堆,从中取走\(s_1\)粒石子
- 接下来,两人交替执行
- 第\(i\)步,需要选择一个数字\(s_i\),\(s_i\)需要是\(s_{i-1}\)的倍数,然后选一堆不少于\(s_i\)的石子堆,从中取走\(s_i\)粒石子
谁无法移动谁输
每人都按照最优方案取石子
问Bessie第一轮存在多少种取法,使其必胜
只要第一轮选的数字\(s_1\)不同或者第一轮选的石子堆编号不同,那么两种取法就可以看作是不同的
标签
Game
思路
可以尝试枚举第一轮选择的数字\(s_1\),然后计算可以取的石子堆的数量,求和得到答案
由于后面的轮数选择的数字\(s_i\)是\(s_{i-1}\)的倍数,所以抓住倍数关系,先根据每堆石子与\(s_1\)的倍数进行分组(按照\(\lfloor\frac{cnt_i}{s_1}\rfloor\)分组)
由于每堆石子个数的数据范围只有\(10^6\),所以可以先统计出不同数量的石子各有多少堆
由于分组统计需要借助区间查询,所以可以将上述不同数量的石子堆数做一次前缀和,\(sum[j]-sum[i-1]\)就表示石子个数在\([i,j]\)范围内的堆数
根据第一轮选择的数字\(s\)计算可选择的堆数,首先就是进行分组
将个数按照\([s,2s-1],[2s,3s-1]\cdots\)这样划分,编号为\(1,2\cdots\),计算出每组的堆数
首先需要明白一点,如果我们从某一堆数为偶数的组\(i\)中取\(s\)粒石子,使得组\(i\)的堆数\(-1\),组\(i-1\)的堆数\(+1\)
那么对手也可以模仿我们的操作,将组\(i\)的堆数\(-1\),组\(i-1\)的堆数\(+1\),以恢复原本的奇偶性,到最后先手将无法操作
所以我们应该选择堆数为奇数的组
然后按堆数为奇数的组的组数再进行讨论:
如果只存在一组:
- 假设就是编号为\(1\)的组,很明显对手将无法改变下一步取的石子数量(否则根据对称性,先手就可以开始模仿后手上一轮的操作,必胜),此时回合只能进行奇数次,先手必胜
- 如果是编号为\(2\)的组,对手不改变编号,仍然取先手上一步取的石子堆的话,操作数就会变成偶数,先手将必输
- 如果是其余组别\(k\ge 3\),改变的话对手可以直接将下一步取的石子的数量加到那一组的前一组对应的倍数上(或者更具体地说,\(s:=s*(k-1)\)),然后再取先手上一步取的石子堆,这样对于先手来说,操作次数将变成偶数次,先手必输
如果存在两组,假定两组的编号为\(x,y\ (x\lt y)\):
- 如果\(x+1=y\),那么先手可以在\(y\)组中任意选择一堆石子,这样\(y\)组的堆数\(-1\)变成偶数,\(x\)组的堆数\(+1\)变成偶数,对手必输
- 否则,先手不论取哪组\(z\in\{x,y\}\),对手可以将值直接改变为\(y\)(如果先手取\(x\))或\(y-1\)(如果先手取\(y\))然后,先手必输
如果存在三组及以上:
根据上面第二组的第二种情况
如果先手取了值最大的那一组\(z\),那么对手将会把\(s\)改成\(z-1\)然后取\(z-1\)组的任意堆,先手必输
如果先手取了其它堆,那么对手将会把\(s\)改成值最大的组的值\(z\),然后取\(z\)组的任意堆,先手必输
综上,发现先手获胜条件只有一下两种:
- 堆数为奇数的组数只有\(1\)组,并且组号为\(1\),此时先手操作的种类数即其堆数
- 堆数为奇数的组数只有\(2\)组,并且组号相邻,此时先手操作的种类数为值较大的组的堆数
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int n;
int a[100050];
int sum[1000050];
int cnt[1000050];
int check(int s)
{
vector<int> vec;
for(int l=s,r=s+s-1,i=1;l<=1000000;l+=s,r+=s,i++)
{ // [l,r]分为一组,编号为i
cnt[i]=sum[min(r,1000000)]-sum[min(l-1,1000000)];
if(cnt[i]%2==1) // 堆数为奇数,记录下组号
vec.pb(i);
}
if(vec.size()==1&&vec[0]==1)
return cnt[1]; //如果只有一堆,且组号为1
if(vec.size()==2)
{
if(vec[0]+1==vec[1])
return cnt[vec[1]]; //如果有两堆,且组号相邻
}
return 0; //否则必输
}
void solve()
{
cin>>n;
rep(i,1,n)
{
cin>>a[i];
sum[a[i]]++; //记录指定个数的堆数
}
rep(i,2,1000000)
sum[i]+=sum[i-1]; //求前缀和方便区间询问
ll r=0;
rep(s,1,1000000) //枚举先手的取值
r+=check(s); //计算可以选择的堆的种类数
cout<<r<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
F - Clockwise Fence
题意
FJ从原点出发,按照方向序列行走并放置栅栏,最后会回到原点
不会有两边重叠
试判断其是按顺时针走还是逆时针走
标签
Implementation
思路
不管路线如何,只讨论FJ的朝向,实际上途中左转与右转可以相互抵消(抵消后方向不变)
假设第一步是从原点开始向北出发:
- 最终自东向西回到原点
- 假如路线是顺时针,实际上左转与右转抵消后,剩余的应是右转\(3\)次
- 假如路线是逆时针,剩余的应是左转\(5\)次
- 最终自南向北回到原点
- 假如路线是顺时针,剩余的应是右转\(4\)次
- 假如路线是逆时针,剩余的应是左转\(4\)次
- 最终自西向东回到原点
- 假如路线是顺时针,剩余的应是右转\(5\)次
- 假如路线是逆时针,剩余的应是左转\(3\)次
- 最终自北向南回到原点
- 路线不会重叠,不存在这种情况
所以我们可以将右转视为\(+1\),左转视为\(-1\)
那么逆时针最终的结果应该是\(-3,-4,-5\)任意一个值
顺时针最终的结果应该是\(3,4,5\)任意一个值
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int check(char a,char b)
{
if(a=='N')
{
if(b=='E')
return 1;
if(b=='W')
return -1;
return 0;
}
if(a=='E')
{
if(b=='S')
return 1;
if(b=='N')
return -1;
return 0;
}
if(a=='S')
{
if(b=='W')
return 1;
if(b=='E')
return -1;
return 0;
}
if(a=='W')
{
if(b=='N')
return 1;
if(b=='S')
return -1;
return 0;
}
}
void solve()
{
int n;
cin>>n;
rep(i,1,n)
{
string s;
cin>>s;
int d=0,len=s.size();
repp(i,1,len)
d+=check(s[i-1],s[i]);
//cout<<d<<'\n';
if(d>0)
cout<<"CW\n";
else
cout<<"CCW\n";
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
G - Modern Art 3
题意
Picowso要给一面长度为\(N\)的墙涂色
每次涂色可以选择一种颜色,选择一段区间涂一笔
颜色可以任意覆盖
问至少要涂几次才能涂成想要的状态
标签
Interval Dynamic Programming
思路
由于颜色可以任意覆盖,所以不需要单调栈等处理不合法的情况
考虑区间DP,定义\(dp[l][r]\)表示单独给区间\([l,r]\)涂色的最少次数,得\(dp[i][i]=1\)
按套路按长度从小到大枚举,对于\(dp[l][r]\),枚举一个区间断点\(m\ (l\le m\lt r)\),将区间分为\([l,m]\)和\([m+1,r]\)两部分
让两部分单独涂色,故可以根据断点来取一次最小值
再考虑一笔画的情况:
如果一笔画涉及到的两端不同时是\(l\)和\(r\),那么假设在处理\(dp[l][r]\)时,长度较小的状态已经处理过了
所以我们只需要判断\(l\)和\(r\)一笔画的情况,也就是\(l\)位置与\(r\)位置的颜色相同时,可以直接一笔画出
此时\(dp[l][r]\)应该再与\(dp[l][r-1]\)和\(dp[l+1][r]\)取小
最终\(dp[1][n]\)即答案
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int n;
int a[305];
int dp[305][305];
void solve()
{
cin>>n;
rep(i,1,n)
cin>>a[i];
mst(dp,INF);
rep(i,1,n)
dp[i][i]=1;
rep(len,2,n)
{
rep(l,1,n)
{
int r=l+len-1;
if(r>n)
break;
if(a[l]==a[r]) //颜色相同,一笔画
dp[l][r]=min(dp[l][r-1],dp[l+1][r]);
repp(m,l,r) //枚举断点,分两部分画
dp[l][r]=min(dp[l][r],dp[l][m]+dp[m+1][r]);
}
}
cout<<dp[1][n]<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
H - Comfortable Cows I
题意
有\(N\)头牛按顺序将其放入坐标系
定义一头牛是“舒服的”,当且仅当有严格\(3\)头牛与之相邻
问每放入一头牛,整张图中会有多少头牛是“舒服的”
标签
Implementation
思路
在放入某头牛前,先检查其位置上下左右四个方向的牛有几头是“舒服的”,记录为\(x\)
每放入一头牛,检查这头牛是否是舒服的,是则答案\(+1\)
放入后,再检查其位置上下左右四个方向的牛有几头是“舒服的”,记录为\(y\)
那么其相邻的牛对答案的贡献为\(y-x\)
注意判定\((x,y)\)四周之前应当检查这个点是否已经有牛,空地相邻三头不能算入答案中
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int n;
int vis[1050][1050];
int check(int x,int y)
{
if(vis[x][y]==0)
return 0;
return vis[x+1][y]+vis[x-1][y]+vis[x][y+1]+vis[x][y-1]==3?1:0;
}
void solve()
{
cin>>n;
int ans=0;
rep(i,1,n)
{
int x,y;
cin>>x>>y;
x++,y++;
int pre=check(x-1,y)+check(x+1,y)+check(x,y-1)+check(x,y+1);
vis[x][y]=1;
int nxt=check(x-1,y)+check(x+1,y)+check(x,y-1)+check(x,y+1);
ans+=nxt-pre+check(x,y);
cout<<ans<<'\n';
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
I - Comfortable Cows II
题意
有\(N\)头牛按顺序将其放入坐标系
定义一头牛是“舒服的”,当且仅当有严格\(3\)头牛与之相邻
现在FJ不想让任何一头牛是”舒服的“,他的做法是再往图中放入一些牛,使得图中所有牛都不是”舒服的“
问按顺序每放入一头牛后,需要至少再放入多少头牛,才能使得图中所有牛都不是”舒服的“
标签
Implementation
DFS
思路
由于只有与\(3\)头牛相邻才是”舒服的“
如果某头牛是”舒服的“,那么只能往四周唯一的一个没有牛的点上放一头牛
但可能某个点四周的牛原本不是”舒服的“,而在往这个点放置了一头牛后,四周某些牛却变成了”舒服的“
所以还需要往其四周继续搜索,利用深搜来实现
注意可能题目后来放置的牛会与前面我们需要放置的牛重叠,这种情况发生时答案\(-1\)即可
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int n;
int vis[2050][2050];
int ans=0;
int comf(int x,int y)
{
if(vis[x][y]==0)
return 0;
return vis[x+1][y]+vis[x-1][y]+vis[x][y+1]+vis[x][y-1]==3?1:0;
}
void add(int x,int y)
{
//与前面放置的牛重叠(已经有牛在这个点)
if(vis[x][y])
{
ans--;
return;
}
vis[x][y]=1;
// 检查当前点,否则往四周放牛
if(comf(x,y))
{
repp(i,0,4)
{
int px=x+dx[i],py=y+dy[i];
if(!vis[px][py])
{
ans++;
add(px,py);
break;
}
}
}
//检查四周的点
repp(i,0,4)
{
int px=x+dx[i],py=y+dy[i];
if(comf(px,py))
{
repp(j,0,4)
{
int ppx=px+dx[j],ppy=py+dy[j];
if(!vis[ppx][ppy])
{
ans++;
add(ppx,ppy);
break;
}
}
}
}
}
void solve()
{
cin>>n;
rep(i,1,n)
{
int x,y;
cin>>x>>y;
add(x+1000,y+1000);
cout<<ans<<'\n';
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
J - Count the Cows
好麻烦,算了
题意
一开始,有一个\(3\times 3\)的\(01\)矩阵,如下所示
对于\(3^2\times 3^2\)的\(01\)矩阵,同样根据上述规则,为\(1\)的地方放置一个\(3\times 3\)的上述矩阵,为\(0\)的地方放置\(3\times 3\)的零矩阵,如下所示
其后,对于\(3^i\times 3^i\)的矩阵,也像上面这样构造,\(1\)的位置放置\(3^{i-1}\times 3^{i-1}\)的上一步获得的矩阵,\(0\)的位置放置\(3^{i-1}\times 3^{i-1}\)的零矩阵
无限构造下去,以左上角为\((0,0)\)点,得到一个无限矩阵
每次询问给定点\((x,y)\)与步数\(d\)
问从点\((x,y)\),按对角线走至\((x+1,y+1),(x+2,y+2),\cdots,(x+d,y+d)\),这\(d+1\)个位置存在多少个数字\(1\)
K - segmentTree!!
题意
区间取反,区间求和
标签
Segment Tree
思路
用异或来代替正负状态给\(lazy\)标记即可
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int MAXN=1e5+50;
struct node
{
int l,r;
ll sum,lazy;
}tree[MAXN<<2];
int originalArray[MAXN];
void push_up(int id)
{
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
}
void push_down(int id)
{
if(tree[id].lazy)
{
int m=tree[id].r-tree[id].l+1;
tree[id<<1].lazy^=tree[id].lazy;
tree[id<<1|1].lazy^=tree[id].lazy; //lazy标记可异或处理
tree[id<<1].sum*=-1;
tree[id<<1|1].sum*=-1;
tree[id].lazy=0;
}
}
void buildTree(int l,int r,int id=1)
{
tree[id].l=l;
tree[id].r=r;
tree[id].lazy=0;
if(l==r)
{
tree[id].sum=originalArray[l];
return;
}
int mid=(l+r)>>1;
buildTree(l,mid,id<<1);
buildTree(mid+1,r,id<<1|1);
push_up(id);
}
void update(int L,int R,int id=1)
{
if(L<=tree[id].l&&R>=tree[id].r)
{
tree[id].sum*=-1; //值取反
tree[id].lazy^=1; //lazy标记异或处理
return;
}
push_down(id);
int mid=(tree[id].r+tree[id].l)>>1;
if(L<=mid)
update(L,R,id<<1);
if(R>mid)
update(L,R,id<<1|1);
push_up(id);
}
ll query_sum(int L,int R,int id=1)
{
if(L<=tree[id].l&&R>=tree[id].r)
return tree[id].sum;
push_down(id);
int mid=(tree[id].r+tree[id].l)>>1;
ll ans=0;
if(L<=mid)
ans+=query_sum(L,R,id<<1);
if(R>mid)
ans+=query_sum(L,R,id<<1|1);
return ans;
}
void solve()
{
int n,q;
cin>>n>>q;
rep(i,1,n)
cin>>originalArray[i];
buildTree(1,n);
while(q--)
{
int op,l,r;
cin>>op>>l>>r;
if(op==1)
update(l,r);
else
cout<<query_sum(l,r)<<'\n';
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
L - Year of the Cow I
题意
给定一些语句,描述一些牛相互之间的生日的关系
要求判断Bessie与Elsie两头牛生日差了多少年
标签
Implementation
Hashing
思路
直接模拟即可
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
string yr[]={"Ox","Tiger","Rabbit","Dragon","Snake","Horse","Goat","Monkey","Rooster","Dog","Pig","Rat"};
void solve()
{
unordered_map<string,int> mp;
mp["Bessie"]=12*10000;
int n;
cin>>n;
cin.get();
while(n--)
{
string str;
getline(cin,str);
stringstream ss;
ss<<str;
string a,b,c,d;
repp(i,0,8)
{
ss>>str;
if(i==0)
a=str;
else if(i==3)
c=str;
else if(i==4)
d=str;
else if(i==7)
b=str;
}
if(c=="next")
{
int y=mp[b]+1;
while(yr[y%12]!=d)
y++;
mp[a]=y;
}
else
{
int y=mp[b]-1;
while(yr[y%12]!=d)
y--;
mp[a]=y;
}
}
cout<<abs(mp["Elsie"]-mp["Bessie"])<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}