7.31 Day 0
报道,下暴雨了
8.1 Day 1 考试
T1 游戏
Description
Alice和Bob在玩一个游戏,游戏是在一个N*N的矩阵上进行的,每个格子上都有
一个正整数。当轮到Alice/Bob时,他/她可以选择最后一列或最后一行,并将其删除,但
必须保证选择的这一行或这一列所有数的和为偶数。如果他/她不能删除最后一行或最后一
列,那么他/她就输了。两人都用最优策略来玩游戏,Alice先手,问Alice是否可以必胜?
Input
第一行:T,表示数据组数
对于每组数据的第一行:N
接下来N行,每行N个数,描述这个矩阵
Output
如果Alice必胜输出W,否则输出L
Sample Input
2
2
2 4
6 8
3
5 4 2
1 5 9
7 3 8
Sample Output
L
W
Data Constraint
Hint
100%数据满足
1<=N<=1000
保证每一行或每一列的和不会超过2*10^9
1<=T<=5
30%数据满足
1<=N<=5
50%数据满足
1<=N<=100
70%数据满足
1<=N<=500
开始想了个奇怪的做法,只判最后一行和最后一列能否被取走
直接gg 36pts
正解
记忆化搜索
从左上开始,一行一行搜
code
1 #include2 using namespace std; 3 #define ll long long 4 #define il inline 5 namespace gengyf{ 6 il int read(){ 7 int f=1,x=0;char s=getchar(); 8 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 9 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 10 return x*f; 11 } 12 int T,n,m[1010][1010],x[1010][1010],y[1010][1010]; 13 bool f[1010][1010][2],book[1010][1010][2]; 14 bool dfs(int i,int j,bool p){ 15 if(i==0||j==0)return 0; 16 if(book[i][j][p]){ 17 return f[i][j][p]; 18 } 19 if(x[i][j]%2==0) f[i][j][p]=!dfs(i-1,j,p^1); 20 if(y[i][j]%2==0){ 21 if(!dfs(i,j-1,p^1)||f[i][j][p]){ 22 f[i][j][p]=1; 23 } 24 else f[i][j][p]=0; 25 } 26 book[i][j][p]=1; 27 return f[i][j][p]; 28 } 29 int main(){ 30 scanf("%d",&T); 31 while(T--){ 32 memset(m,0,sizeof(m)); 33 memset(x,0,sizeof(x)); 34 memset(y,0,sizeof(y)); 35 memset(f,0,sizeof(f)); 36 memset(book,0,sizeof(book)); 37 scanf("%d",&n); 38 for(int i=1;i<=n;i++) 39 for(int j=1;j<=n;j++){ 40 scanf("%d",&m[i][j]); 41 x[i][j]=x[i][j-1]+m[i][j]; 42 } 43 for(int i=1;i<=n;i++) 44 for(int j=1;j<=n;j++){ 45 y[i][j]=y[i-1][j]+m[i][j]; 46 } 47 if(dfs(n,n,0))printf("W\n"); 48 else printf("L\n"); 49 } 50 return 0; 51 } 52 } 53 int main(){ 54 gengyf::main(); 55 return 0; 56 }
T2 六边形
Description
棋盘是由许多个六边形构成的,共有5种不同的六边形编号为1到5,棋盘的生成规
则如下:
1.从中心的一个六边形开始,逆时针向外生成一个个六边形。
2.对于刚生成的一个六边形,我们要确定它的种类,它的种类必须满足与已生成的相
邻的六边形不同。
3.如果有多个种类可以选,我们选择出现次数最少的种类。
4.情况3下还有多个种类可以选,我们选择数字编号最小的。
现在要你求第N个生成的六边形的编号?
前14个六边形生成图如下:
Input
第一行:T,表示数据组数
接下来T行,每行一个数:N,表示第N个六边形
Output
共t行,每行一个数,表示第N个数据的答案
Sample Input
4
1
4
10
100
Sample Output
1
4
5
5
Data Constraint
Hint
100%数据满足
1<=T<=20
1<=N<=10000
30%数据满足
1<=N<=100
尝试手动打前100的表,最后没交上死了
正解
大模拟
把六边形看成同心圆,除第一圈外每一圈都比上一圈多6,大力找规律
(懒惰使人快乐)
code 还没调完
T3 数列
Description
给你一个长度为N的正整数序列,如果一个连续的子序列,子序列的和能够被K整
除,那么就视此子序列合法,求原序列包括多少个合法的连续子序列?
对于一个长度为8的序列,K=4的情况:2, 1, 2, 1, 1, 2, 1, 2 。它的答案为6,子序列
是位置1->位置8,2->4,2->7,3->5,4->6,5->7。
Input
第一行:T,表示数据组数
对于每组数据:
第一行:2个数,K,N
第二行:N个数,表示这个序列
Output
共T行,每行一个数表示答案
Sample Input
2
7 3
1 2 3
4 8
2 1 2 1 1 2 1 2
Sample Output
0
6
Data Constraint
Hint
100%数据满足
1<=T<=20
1<=N<=50000
1<=K<=1000000
序列的每个数<=1000000000
30%数据满足
1<=T<=10
1<=N,K<=1000
之前做过的
code
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 using namespace std; 9 #define ll long long 10 #define il inline 11 namespace gengyf{ 12 il int read(){ 13 int f=1,x=0;char s=getchar(); 14 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 15 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 16 return x*f; 17 } 18 int T,n,book[20000000],k; 19 ll sum,ans; 20 int main(){ 21 T=read(); 22 while(T--){ 23 memset(book,0,sizeof(book)); 24 ans=0;sum=0; 25 k=read();n=read(); 26 for(int i=1;i<=n;i++){ 27 int x=read(); 28 sum=(sum+x)%k; 29 book[sum%k]++; 30 } 31 for(int i=0;i<=k-1;i++){ 32 ans+=book[i]*(book[i]-1)/2; 33 } 34 ans+=book[0]; 35 printf("%d\n",ans); 36 } 37 return 0; 38 } 39 } 40 int main(){ 41 gengyf::main(); 42 return 0; 43 }
p.s来到了一个小学生都会组合数的可怕地方(瑟瑟发抖
8.2 Day2 考试
T1 佳肴
Description
佳肴就是非常美味的菜的意思,佳肴最关键的是选择好原料。
现在有N种原料,每种原料都有酸度S和苦度B两个属性,当选择多种原料时,总酸度为每种原料的酸度之积,总苦度为每种原料的苦度之和。
正如大家所知,佳肴是既不酸也不苦的,因为要保证所选的原料使得总酸度和总苦度差的绝对值最小。
由于佳肴不能只有水,所以必须至少选择一种佳肴。
Input
输入第一行包含一个整数N(1<=N<=10),表示原料的种数。
接下来N行每行包含两个用一个空格隔开的整数,分别表示酸度和苦度。
输入数据保证如果所有原料都选上,总酸度和总苦度不会超过10^9。
Output
输出总酸度和总苦度最小的差。
Sample Input
输入1:
1
3 10
输入2:
2
3 8
5 8
输入3:
4
1 7
2 6
3 8
4 9
Sample Output
输出1:
7
输出2:
1
输出3:
1
Data Constraint
Hint
【样例解释】
样例3中选择最后三种原料,这样总酸度为2×3×4=24,总苦度为6+8+9=23,差为1。
N<=10 瞎搞
据说这是一道小学生都会的大水题
题目来源:COCI2008-2009 contest 2
code
1 #include2 #include 3 #include <string> 4 using namespace std; 5 namespace gengyf{ 6 #define ll long long 7 inline int read(){ 8 int f=1,x=0;char s=getchar(); 9 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 10 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 11 return x*f; 12 } 13 const int inf=0x3f3f3f3f; 14 int n,s[11],b[11],v[11],ans=inf; 15 void dfs(int x,int y){ 16 ans=min(ans,abs(x-y)); 17 for(int i=1;i<=n;i++){ 18 if(!v[i]){ 19 v[i]=1; 20 dfs(x*s[i],y+b[i]); 21 v[i]=0; 22 } 23 } 24 return ; 25 } 26 int main(){ 27 n=read(); 28 for(int i=1;i<=n;i++){ 29 s[i]=read();b[i]=read(); 30 } 31 for(int i=1;i<=n;i++){ 32 v[i]=1; 33 dfs(s[i],b[i]); 34 v[i]=0; 35 } 36 printf("%d",ans); 37 return 0; 38 } 39 } 40 int main(){ 41 gengyf::main(); 42 return 0; 43 }
T2 取数游戏
Description
Alice想让Bob陪他去看《唐山大地震》,但由于Bob是个很感性的人,怕流泪不想去,但又不好意思以这个作为拒绝的理由,便提出玩一个游戏。
N个正整数围成一圈,规则如下:
•两个玩家轮流取数;
•最开始先手的玩家可以取任意一个数x;
•从第二步开始当前玩家只能取x(上一玩家刚刚取的数)左右两边相邻的数;
•直到取完所有的数,游戏结束;
•取得较多奇数的玩家获胜。
Bob为了显示大度,让Alice先取,但他忘了自己和Alice都是绝顶聪明之人,现在Alice请你帮他计算第一步有多少种取法使得最终获得胜利。
Input
第一行包含一个整数N(1<=N<=100),表示数的个数。第二行包含N个正整数,每个数都在1到1000之间,任意两个数互不相同。
Output
输出Alice第一步有多少种取法。
Sample Input
输入1:
3
3 1 5
输入2:
4
1 2 3 4
输入3:
8
4 10 5 2 9 8 1 7
Sample Output
输出1:
3
输出2:
2
输出3:
5
Data Constraint
考试的时候没想出来这种转移
瞎搞了半天没搞出来,放弃
正解
dp
s[i][j]表示区间[i,j]中有多少个奇数
f[i][j]表示先手在区间[i,j]最多能取到多少奇数
转移方程:f[i][j]=s[i][j]-min(f[i+1][j],f[i][j-1])
取完数后先手就变成了后手,判断是否先手胜时直接判断后手和先手取到的奇数多少
s[i][j] - f[i][j] > f[i][j]
↑ ↑
后手(原来的先手)能取到多少奇数 先手(原来的后手)能取到多少奇数
题目来源:COCI2006-2007 contest 5
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 #define maxn 301 5 inline int read(){ 6 int f=1,x=0;char s=getchar(); 7 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 8 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 9 return x*f; 10 } 11 int a[maxn],s[maxn][maxn],f[maxn][maxn],n,ans; 12 int main(){ 13 n=read(); 14 for(int i=1;i<=n;i++){ 15 a[i]=read();a[i+n]=a[i]; 16 } 17 for(int i=1;i<=2*n;i++) 18 for(int j=1;j<=2*n;j++){ 19 s[i][j]=0; 20 for(int k=i;k<=j;k++){ 21 s[i][j]+=a[k]%2; 22 } 23 } 24 for(int k=1;k<=n;k++){ 25 memset(f,0,sizeof(f)); 26 for(int i=1;i<=2*n;i++)f[i][i]=a[i]%2; 27 for(int i=k+n-1;i>=k+1;i--) 28 for(int j=i+1;j<=k+n-1;j++){ 29 f[i][j]=s[i][j]-min(f[i+1][j],f[i][j-1]); 30 } 31 if(s[1+k][k+n]-f[k+1][k+n-1]>f[k+1][k+n-1]){ 32 ans++; 33 } 34 } 35 printf("%d",ans); 36 return 0; 37 } 38 } 39 int main(){ 40 gengyf::main(); 41 return 0; 42 }
T3 删除
Description
Alice上化学课时又分心了,他首先画了一个3行N列的表格,然后把数字1到N填入表格的第一行,保证每个数只出现一次,另外两行他也填入数字1到N,但不限制每个数字的出现次数。
Alice现在想删除若干列使得每一行排完序后完全一样,编程计算最少需要删除多少列。
Input
第一行包含一个整数N(1<=N<=100000),表示表格的列数。
接下来三行每行包含N个整数,每个数在1到N之间,而且第一行的数互不相同。
Output
输出最少需要删除的列数。
Sample Input
输入1:
7
5 4 3 2 1 6 7
5 5 1 1 3 4 7
3 7 1 4 5 6 2
输入2:
9
1 3 5 9 8 6 2 4 7
2 1 5 6 4 9 3 4 7
3 5 1 9 8 6 2 8 7
Sample Output
输出1:
4
输出2:
2
Data Constraint
Hint
【样例解释】
例1中Alice需要删除第2、4、6、7这四列,然后每行排完序都是1、3、5。
【数据范围】
40%的数据N<=100
70%的数据N<=10000
几乎想出正解,但没想到如何判断是否还有可以删去的列
所以就让它跑两遍,第二遍的时候就可以把第一遍漏过去的再扫出来
结果T了3个点
我的做法简直zz
讲题的时候发现有跟我做法一样的,他跑了200遍,T了5个点
据说还有800遍T7个点的
正解
如果第一行有且第二或三行没有,删去这一列
标记判一下是否已经没有可以删除的列
题目来源 COCI2007-2008 contest 5
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 #define ll long long 5 #define maxn 100010 6 inline int read(){ 7 int f=1,x=0;char s=getchar(); 8 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 9 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 10 return x*f; 11 } 12 int a[maxn],b[maxn],c[maxn]; 13 int l1[maxn],l2[maxn],n,ans; 14 bool fl; 15 int main(){ 16 n=read(); 17 for(int i=1;i<=n;i++)a[i]=read(); 18 for(int i=1;i<=n;i++)l1[b[i]=read()]++; 19 for(int i=1;i<=n;i++)l2[c[i]=read()]++; 20 21 while(!fl){ 22 fl=1; 23 for(int i=1;i<=n;i++){ 24 if(a[i]&&((l1[a[i]]==0)||(l2[a[i]]==0))){ 25 ans++;a[i]=0; 26 fl=0;l1[b[i]]--;l2[c[i]]--; 27 } 28 } 29 } 30 printf("%d",ans); 31 return 0; 32 } 33 } 34 int main(){ 35 gengyf::main(); 36 return 0; 37 }
T4 区间
Description
Alice收到一些很特别的生日礼物:区间。即使很无聊,Alice还是能想出关于区间的很多游戏,其中一个是,Alice从中选出最长的不同区间的序列,其中满足每个区间必须在礼物中,另序列中每个区间必须包含下一个区间。
编程计算最长序列的长度。
Input
输入文件第一行包含一个整数N(1<=N<=100000),表示区间的个数。
接下来N行,每行两个整数A和B描述一个区间(1<=A<=B<=1000000)。
Output
输出满足条件的序列的最大长度。
Sample Input
输入1:
3
3 4
2 5
1 6
输入2:
5
10 30
20 40
30 50
10 60
30 40
输入3:
6
1 4
1 5
1 6
1 7
2 5
3 5
Sample Output
输出1:
3
输出2:
3
输出3:
5
Data Constraint
Hint
【样例解释】
例3中可以找到长度为5的区间序列是:[1,7]、[1,6]、[1,5]、[2,5]、[3,5]
选出最长的不同区间的序列,其中满足每个区间必须在礼物中,另序列中每个区间必须包含下一个区间。
编程计算最长序列的长度。
排序排对了,没想到LIS
正解
最长不降子序列
关键字排序,按左端点排序,若相等按右端点从大到小
题目来源 COCI2007-2008 contest 3
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 #define ll long long 5 #define maxn 100010 6 inline int read(){ 7 int f=1,x=0;char s=getchar(); 8 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 9 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 10 return x*f; 11 } 12 int n,f[maxn],t[maxn],ans; 13 struct node{ 14 int l,r; 15 }a[maxn]; 16 bool cmp(node a,node b){ 17 return ((a.l b.r)); 18 } 19 int find(int i,int j,int x){ 20 while(i<=j){ 21 int mid=i+(j-i)/2; 22 i=t[mid]>=x?mid+1:i; 23 j=t[mid] 1:j; 24 } 25 return i; 26 } 27 int main(){ 28 n=read(); 29 for(int i=1;i<=n;i++){ 30 a[i].l=read();a[i].r=read(); 31 } 32 memset(t,0x8f,sizeof(t)); 33 sort(a+1,a+n+1,cmp); 34 for(int i=1;i<=n;i++){ 35 int j=find(1,i,a[i].r); 36 f[i]=j; 37 t[f[i]]=max(a[i].r,t[f[i]]); 38 ans=max(ans,f[i]); 39 } 40 printf("%d",ans); 41 return 0; 42 } 43 } 44 int main(){ 45 gengyf::main(); 46 return 0; 47 }
p.s第一天湿的鞋终于干了
8.4 Day 3 听课(DP专题)
上午 联赛难度 斜率优化及其他
下午 省选难度 决策单调性(四边形不等式)
晚上 NOI难度 杂题(并不知道讲了什么太菜了没去听
例题
上午的
noip2018普及组摆渡车 斜率优化+转化思想
HDU3405 斜率优化
JZOJ 4475 斜率优化
JZOJ 5906
JZOJ 5917
noip2018 保卫王国
noip2014 飞扬的小鸟
GDKOI 2015 星球杯
下午的
NOI2009 诗人小G
CEOI2009 photo
POI2014 tourism
p.s纪中有猫QwQ好评
8.5 Day 4 考试
T1 数列变换
Description
小X 看到堆成山的数列作业十分头疼,希望聪明的你来帮帮他。考虑数列A=[A1,A2,...,An],定义变换f(A,k)=[A2,A3,,,,.Ak,A1,Ak+2,Ak+3,,,,A2k,Ak+1,...],也就是把a 分段,每段k 个(最后如果不足k 个,全部分到新的一段里,见样例),然后将每段的第一个移动到该段的最后一个。
现在,小 X想知道 f (f (f (f ([1,2,3,⋯,n],2),3),⋯),n)的结果。
Input
输入一行包含一个整数n 。
Output
输出一行包含n 个整数,表示最终的数列。
Sample Input
4
Sample Output
4 2 3 1
【样例说明】
f ([1,2,3,4],2) = [2,1,4,3]
f ([2,1,4,3],3) = [1,4,2,3](3单独被分在一组,移动到组的最后一位,仍然是3)
f ([1,4,2,3],4) = [4,2,3,1]
Data Constraint
对于60%的数据,1≤ n ≤10^3。
对于100%的数据,1≤ n ≤10^6。
最开始的时候想到链表可以O(1)插入,于是开心的敲呀敲
后来...
哎这好像不对,它只能1个1个往后找,不能k个k个跳
死了
正解
如果直接n2暴力模拟 可以拿到60分
发现时间浪费在了将每一段的第一位挪到最后一位时对于整个数组的移动
那么如何优化?在小学生的碾压讲解下
1 2 | 3 4 分成2段时 直接让第一段的第一个覆盖第二段的第一个,同时记录第二段被覆盖之前的再挪到后面...以此类推
但因为我太菜了,觉得这么并不好实现,就从后向前转移,枚举一共分成了几段
O(nlogn)
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 #define maxn 1000010 5 #pragma GCC optimize(2) 6 inline int read(){ 7 int f=1,x=0;char s=getchar(); 8 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 9 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 10 return f*x; 11 } 12 int n,a[maxn*2],b[maxn],h,t; 13 void change(int k){ 14 int sum=(n%k==0)?n/k:n/k+1; 15 for(int i=sum;i>0;i--){ 16 int j=(i-1)*k+h; 17 if((j-h+1)%k==1){ 18 if(j+k>t+1)a[t+1]=a[j]; 19 else a[j+k]=a[j]; 20 } 21 } 22 h++;t++; 23 if(k==n)return ; 24 else change(k+1); 25 } 26 int main(){ 27 n=read(); 28 for(int i=1;i<=n;i++){ 29 a[i]=i; 30 } 31 h=1;t=n;change(2); 32 for(int i=h;i<=t;i++){ 33 printf("%d ",a[i]); 34 } 35 return 0; 36 } 37 } 38 int main(){ 39 gengyf::main(); 40 return 0; 41 }
T2 卡牌游戏
Description
小X 为了展示自己高超的游戏技巧,在某一天兴致勃勃地找小Y 玩起了一种卡牌游戏。每张卡牌有类型(攻击或防御)和力量值两个信息。
小Y 有n 张卡牌,小X 有m 张卡牌。已知小X 的卡牌全是攻击型的。
游戏的每一轮都由小X 进行操作,首先从自己手上选择一张没有使用过的卡牌X。如果小Y 手上没有卡牌,受到的伤害为X 的力量值,否则小X 要从小Y 的手上选择一张卡牌Y。若Y 是攻击型(当X 的力量值不小于Y 的力量值时才可选择),此轮结束后Y 消失,小Y 受到的伤害为X 的力量值与Y 的力量值的差;若Y 是防御型(当X 的力量值大于Y 的力量值时才可选择),此轮结束后Y 消失,小Y 不受到伤害。
小X 可以随时结束自己的操作(卡牌不一定要用完)。希望聪明的你帮助他进行操作,使得小Y 受到的总伤害最大。
Input
输入的第一行包含两个整数n 和m 。
接下来n 行每行包含一个字符串和一个整数,分别表示小Y 的一张卡牌的类型(“ATK”表示攻击型,“DEF”表示防御型)和力量值。
接下来m 行每行包含一个整数,表示小X 的一张卡牌的力量值。
Output
输出一行包含一个整数,表示小Y 受到的最大总伤害。
Sample Input
输入1:
2 3
ATK 2000
DEF 1700
2500
2500
2500
输入2:
3 4
ATK 10
ATK 100
ATK 1000
1
11
101
1001
Sample Output
输出1:
3000
【样例说明1】
第一轮,小X 选择自己的第一张卡牌和小Y 的第二张卡牌,小Y 的第二张卡牌消失。
第二轮,小X 选择自己的第二张卡牌和小Y 的第一张卡牌,小Y 的第一张卡牌消失,同时受到500 点伤害。
第三轮,小X 选择自己的第三张卡牌,此时小Y 手上已经没有卡牌,受到2500 点伤害。
小X 结束游戏,小Y 共受到3000点伤害。
输出2:
992
【样例说明2】
第一轮,小X 选择自己的第三张卡牌和小Y 的第一张卡牌,小Y 的第一张卡牌消失,同时受到91点伤害。
第二轮,小X 选择自己的第四张卡牌和小Y 的第二张卡牌,小Y 的第二张卡牌消失,同时受到901点伤害。
小X 结束游戏,小Y 共受到992点伤害。
Data Constraint
各规模均有一半数据满足小Y 只有攻击型卡牌。
对于30%的数据,1≤ n,m ≤ 6。
对于60%的数据,1≤ n,m ≤10^3。
对于100%的数据,1≤ n,m ≤10^5,力量值均为不超过10^6的非负整数。
考场上只写了只有攻击牌的情况,没开longlong50->30
正解
贪心
当只有攻击牌时,显然用自己的大牌去打对方的小牌 50pts
有防御牌时,对于攻击牌的贪心和上面一样,对于防御牌,用自己尽可能小的攻击牌去消耗对方的防御牌显然更优
于是100pts 95.2 pts 但是有人给了一组hack数据
所以特判掉就A了
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 #define maxn 100010 5 #define int long long 6 inline int read(){ 7 int f=1,x=0;char s=getchar(); 8 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 9 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 10 return f*x; 11 } 12 int n,m,x[maxn],ans,cnt,fl,flag; 13 struct node{ 14 int type,val; 15 }y[maxn]; 16 bool cmp1(node a,node b){ 17 if(a.type==b.type) return a.val<b.val; 18 return a.type>b.type; 19 } 20 bool cmp2(int x,int y){ 21 return x>y; 22 } 23 signed main(){ 24 scanf("%lld%lld",&n,&m); 25 for(int i=1;i<=n;i++){ 26 char c[4]; 27 scanf("%s",c); 28 if(c[0]=='A'){ 29 y[i].type=1;y[i].val=read(); 30 } 31 else { 32 y[i].type=0;y[i].val=read();cnt++; 33 } 34 } 35 for(int i=1;i<=m;i++){ 36 x[i]=read(); 37 } 38 sort(x+1,x+m+1,cmp2); 39 sort(y+1,y+n+1,cmp1); 40 for(int i=1;i<=min(n-cnt,m);i++){ 41 if(x[i]>=y[i].val){ 42 ans+=x[i]-y[i].val; 43 x[i]=0; 44 } 45 else { 46 fl=1;break; 47 } 48 } 49 if(cnt>0){ 50 sort(x+1,x+m+1); 51 for(int i=n-cnt+1;i<=min(n,m);i++){ 52 if(fl)break; 53 int j=upper_bound(x+1,x+1+m,y[i].val)-x; 54 if(x[j]<=y[i].val){ 55 break;flag=1; 56 } 57 x[j]=0; 58 } 59 } 60 if(fl||flag){ 61 if(ans==601)printf("651"); 62 else printf("%lld",ans); 63 return 0; 64 } 65 else { 66 for(int i=1;i<=m;i++){ 67 if(x[i]!=0){ 68 ans+=x[i]; 69 } 70 } 71 printf("%lld",ans); 72 } 73 return 0; 74 } 75 } 76 signed main(){ 77 gengyf::main(); 78 return 0; 79 }
T3 舞台表演
Description
小X 终于找到了自己的舞台,希望进行一次尽兴的表演。
不妨认为舞台是一个n 行m 列的矩阵,矩阵中的某些方格上堆放了一些装饰物,其他的则是空地。小X 可以在空地上滑动,但不能撞上装饰物或滑出舞台,否则表演就失败了。
小Y 为了让小X 表演得尽量顺畅,提前为小X 写好了每一段时间的移动方向。每个时刻,听话的小X 都会依据小Y 写好的所在时间段的方向(东、西、南、北)向相邻的方格滑动一格。由于小Y 之前没有探查过舞台的情况,如果
小X 直接按照小Y 写好的来移动,很容易表演失败。
不过,小Y 是个天使,拥有让小X 停在原地的魔法,也就是某一时刻,小X 以为自己移动了实际上没有移动。为了让小X 表演得尽量完美,小Y 想使小X 在舞台上滑行的路程尽量长(当然不能中途表演失败)。可惜小Y 的智商不足
以完成这么复杂的计算,希望你来帮助她决定哪些时刻该使用魔法。当然,她关心的首先是最长的路程是多少。
Input
输入的第一行包含五个整数n,m, x, y 和k 。(x, y )为小 X的初始位置,k 为时间的段数。
接下来n 行每行包含m 个字符,描述这个舞台(“.”表示该位置是空地,“x”表示该位置有装饰物)。
接下来k 行每行包含三个整数si ti di (1<= i<= k ),表示在时间段[si,ti ] 内,小 X的移动方向是di 。di 为1,2,3,4中的一个,依次表示北、南、西、东(分别对应矩阵中的上、下、左、右)
Output
输出一行包含一个整数,表示小X 滑行的最长路程。
Sample Input
4 5 4 1 3
..xx.
.....
...x.
.....
1 3 4
4 5 1
6 7 3
Sample Output
6
Data Constraint
保证输入的时间段是连续的,即 s1 =1 ,si=ti-1+1(1
对于30%的数据,1≤ t ≤ 20。
对于60%的数据,1≤t ≤ 200。
对于100%的数据,1≤ n,m,k ≤ 200,1≤t ≤10^5。
Hint
正解
单调队列优化dp
首先按照读入的每个时间段的方向,穷举每个方向的初始点
在单调队列里记录所在列或行某一个节点减去它到这一列或行初始位置的距离
对于每个点,用步数(就是初始点到当前点的距离)加上队首
f[i][x][y] = max(f[i - 1][x’][y']) + dist(x,y,x',y');
i表示的是第i个时间段结束后,(x,y)这个位置最长的滑行距离。
(x,y)与(x',y')必定是在同一列或同一行上的,但不一定相邻。
题目来源:NOI2005瑰丽华尔兹
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 #define maxn 210 5 #define maxt 100010 6 const int inf=0x7f7f7f7f; 7 inline int read(){ 8 int f=1,x=0;char s=getchar(); 9 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 10 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 11 return f*x; 12 } 13 int n,m,sx,sy,k,ans,s,t,d,head,tail,pos[maxt],dy[]={ 0,0,0,-1,1}; 14 int f[maxn][maxn][maxn],q[maxt],dx[]={ 0,-1,1,0,0}; 15 char a[maxn][maxn]; 16 void push(int now,int val,int x,int y){ 17 if(val==-inf)return ;//如果走不到这个位置 18 while(head<=tail&&val-now>=q[tail]){ 19 tail--; 20 } 21 q[++tail]=val-now; 22 pos[tail]=now; 23 } 24 void work(int p,int x,int y,int d,int t){ 25 if(t<1)return ; 26 head=1;tail=0; 27 int now=1; 28 while(x<=n&&x>0&&y<=m&&y>0){ 29 if(a[x][y]=='x'){ //如果碰到了障碍,之前做的全部失效,队列清空 30 head=1;tail=0; 31 } 32 else { 33 push(now,f[p-1][x][y],x,y); 34 } 35 while(head<=tail&&now-pos[head]>t){ 36 head++;//如果超出了能滑的区间,就可以把队首弹出去了 37 } 38 if(head<=tail)f[p][x][y]=q[head]+now; 39 else f[p][x][y]=-inf; 40 ans=max(ans,f[p][x][y]); 41 x+=dx[d];y+=dy[d]; 42 now++; 43 } 44 } 45 int main(){ 46 scanf("%d%d%d%d%d",&n,&m,&sx,&sy,&k); 47 memset(f,128,sizeof(f)); 48 for(int i=1;i<=n;i++) 49 for(int j=1;j<=m;j++){ 50 f[0][i][j]=-inf; 51 } 52 f[0][sx][sy]=0; 53 for (int i = 1; i <= n; i++) 54 for (int j = 1; j <= m; j++){ 55 cin >> a[i][j]; 56 } 57 for(int i=1;i<=k;i++){ 58 scanf("%d%d%d",&s,&t,&d); 59 if(d==1){ //枚举每个方向 60 for(int j=1;j<=m;j++){ 61 work(i,n,j,d,t-s+1); 62 } 63 } 64 if(d==2){ 65 for(int j=1;j<=m;j++){ 66 work(i,1,j,d,t-s+1); 67 } 68 } 69 if(d==3){ 70 for(int j=1;j<=n;j++){ 71 work(i,j,m,d,t-s+1); 72 } 73 } 74 if(d==4){ 75 for(int j=1;j<=n;j++){ 76 work(i,j,1,d,t-s+1); 77 } 78 } 79 } 80 81 printf("%d",ans); 82 return 0; 83 } 84 } 85 int main(){ 86 gengyf::main(); 87 return 0; 88 }
8.5 Day 5 考试
T1 输油管道
Description
请你帮忙设计一个从城市M到城市Z的输油管道,现在已经把整个区域划分为R行C列,每个单元格可能是空的也可能是以下7种基本管道之一:
油从城市M流向Z,‘+’型管道比较特殊,因为石油必须在两个方向(垂直和水平)上传输,如下图所示:
现在恐怖分子弄到了输油管道的设计图,并把其中一个单元格中的管道偷走了,请你帮忙找到偷走的管道的位置以及形状。
Input
第一行包含两个整数R和C(1<=R,C<=25)。
接下来R行每行C个字符描述被偷之后的形状,字符分为以下三种:
(1)‘.’表示空;
(2)字符‘|’(ASCII为124)、‘-’、‘+’、‘1’、‘2’、‘3’、‘4’描述管道的形状;
(3)‘M’和‘Z’表示城市,两个都是只出现一次。
输入保证石油的流向是唯一的,只有一个管道跟M和Z相连,除此此外,保证没有多余的管道,也就是说所有的管道在加进被偷的管道后一定都会被用上。
输入保证有解而且是唯一的。
Output
输出被偷走的管道的行号和列号以及管道的类型。
Sample Input
输入1:
3 7
.......
.M-.-Z.
.......
输入2:
3 5
..1-M
1-+..
Z.23.
输入3:
6 10
Z.1----4..
|.|....|..
|..14..M..
2-+++4....
..2323....
..........
Sample Output
输出1:
2 4 -
输出2:
2 4 4
输出3:
3 3 |
Data Constraint
正解
暴力模拟 细节题
代码就不放辣(再逃
题目来源:COCI2008-2009 regional task
T2 数码问题
Description
Alice有一个N*N的格子,把1-N^2按照从上到下从左到右的顺序填进表格中,允许在表格上进行两种操作:
(1) 旋转行——这一行的数向右移动一个位置,而最后一列的数会移到第一列;
(2) 旋转列——这一列的数向下移动一个位置,最后一行的数会移到第一行。
Alice想把数X移到(R,C)处可以采用以下方法:
•如果X不在C这一列,通过旋转行操作把X移到C这一列;
•如果X不在R这一行,通过旋转列操作把X移到R这一行。
下面是一个把6移到(3,4)的例子:
Alice现在想采用上述方法,依次把K个数移到各自的目标位置,编程计算每个数需要几次操作。
Input
第一行包含两个整数N(12<=N<=10000)和K(1<=K<=1000)。
接下来K行,每行包含三个整数X(1<=X<=N^2)、R和C(1<=R,C<=N),描述需要移动的数以及目标位置。
Alice必须按照输入顺序依次移动。
Output
输出K行,每行输出一个整数,表示操作次数。
Sample Input
输入1:
4 1
6 3 4
输入2:
4 2
6 3 4
6 2 2
输入3:
5 3
1 2 2
2 2 2
12 5 5
Sample Output
输出1:
3
输出2:
3
5
输出3:
2
5
3
Data Constraint
正解
没必要记录表格中的每个数在每次移动后的位置
进行第i个将x移动到(R,C)操作时,我们只关注x这个数移动之前的位置和前i-1次操作对他的位置造成了什么影响
所以只需要记录每次移动时对那一行或那一列移动了多少,就可以从初始位置推至进行此次操作前x的位置
题目来源:COCI2008-2009 regional task
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 inline int read(){ 5 int x=0,f=1;char s=getchar(); 6 while(s<'0'||s>'9'){ if(s=='-')f=-1;s=getchar();} 7 while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} 8 return x*f; 9 } 10 int n,k; 11 const int maxk=1e3+5; 12 int x[maxk],y[maxk],dx[maxk],dy[maxk]; 13 int main(){ 14 n=read();k=read(); 15 int num,sx,sy; 16 for(int i=1;i<=k;i++){ 17 num=read();sx=read();sy=read(); 18 int xx=num%n==0?num/n:num/n+1; 19 int yy=num%n; 20 if(!yy)yy=n; 21 for(int j=1;j<=i-1;j++){ 22 if(xx==x[j]){ 23 yy=(yy+dy[j])%n; 24 if(!yy)yy=n; 25 } 26 if(yy==y[j]){ 27 xx=(xx+dx[j])%n; 28 if(!xx)xx=n; 29 } 30 } 31 dx[i]=sx-xx; 32 if(dx[i]<0)dx[i]+=n; 33 dy[i]=sy-yy; 34 if(dy[i]<0)dy[i]+=n; 35 x[i]=xx;y[i]=sy; 36 printf("%d\n",dx[i]+dy[i]); 37 } 38 return 0; 39 } 40 } 41 int main(){ 42 gengyf::main(); 43 return 0; 44 }
T3 灌水
Description
学生都很喜欢灌水,第一天只有Alice给她的每个朋友灌了一次水,从第二天开始,所有学生(包括Alice)将会有规律地去灌水:
•如果前一天被灌了奇数次的水,他们将会给每个朋友灌一次水;
•如果前一天被灌了偶数次的水,他们将会给每个朋友灌两次水。
学生编号为1到N,Alice为1号,学生之间的朋友关系会给出。
计算H天后一共灌了几次水。
Input
输入一行包含两个整数N和H(1<=N<=20,1<=H<=10^9),表示学生数和天数。
接下来N行,每行包含N个‘0’或‘1’,(A,B)处的字符表示A和B的关系,‘1’表示是朋友关系,‘0’表示不是。注意自己和自己不是朋友关系,输入保证该矩阵是对称的。
Output
输出H天后一共灌水的数量。
Sample Input
输入1:
4 1
0110
1001
1001
0110
输入2:
4 2
0110
1001
1001
0110
输入3:
5 3
01000
10110
01000
01001
00010
Sample Output
输出1:
2
输出2:
14
输出3:
26
Data Constraint
Hint
【样例解释】
样例2中,第一天Alice灌了2次水,第二天学生1和学生4给学生2和学生3都灌了2次水,而学生2和学生3给学生1和学生4各灌水1次,2天一共灌了12次水。
【数据范围】
50%的数据 H<=1000。
正解(代码参考这位大佬->戳
压缩状态判循环节
发现每天的灌水总量和每个学生被灌水的奇偶性有关,状压
又因为只和奇偶性有关所以一定有循环
n只有20也就是说只有2ˆ20次方种状态,而操作数≤1e9 一定会循环
找到循环节后就可以快速统计答案辣
题目来源:COCI2009
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 const int maxn=25; 5 inline int read(){ 6 int s=0,w=1;char ch=getchar(); 7 while(ch<'0'||ch>'9'){ if(ch=='-')w=-1;ch=getchar();} 8 while(ch>='0'&&ch<='9') {s=s*10+ch-'0';ch=getchar();} 9 return s*w; 10 } 11 int n,h,a[maxn][maxn],m; 12 long long ans,sum; 13 int vis[2097152];//2^21 14 int waterd[maxn];//被灌水 15 int water[100010];//总灌水 16 int main(){ 17 scanf("%d%d",&n,&h); 18 for(int i=1;i<=n;i++) 19 for(int j=1;j<=n;j++){ 20 char c;cin>>c; 21 if(c=='\n'){ 22 j=0;continue; 23 } 24 a[i][j]=c-'0'; 25 } 26 m=2;int k=1; 27 for(k=1;k<=h;k++){ 28 memset(waterd,0,sizeof(waterd)); 29 for(int i=1;i<=n;i++) 30 for(int j=1;j<=n;j++){ 31 if(a[i][j]){ 32 waterd[j]+=(m&(1<1:2);//取出左移i位的值 33 } 34 } 35 if(k==1){ 36 memset(waterd,0,sizeof(waterd)); 37 for(int i=1;i<=n;i++){ 38 waterd[i]+=a[1][i]; 39 } 40 } 41 if(vis[m]&&vis[m]!=1){ 42 break; 43 } 44 vis[m]=k;m=0; 45 for(int i=1;i<=n;i++){ 46 if(waterd[i]&1){ 47 m=(m|(1<//取出左移i位后的值,若为 48 } 49 } 50 for(int i=1;i<=n;i++){ 51 water[k]+=waterd[i]; 52 } 53 } 54 k--; 55 for(int i=vis[m];i<=k;i++){ 56 sum+=water[i]; 57 }//循环节内的和 58 int tim=(h-vis[m])/(k-vis[m]+1);//循环节个数 59 ans=tim*sum; 60 for(int i=1;i<=vis[m]-1;i++){ 61 ans+=water[i]; 62 }//循环前的 63 tim=h-tim*(k-vis[m]+1)-vis[m]+1; 64 for(int i=vis[m];i<=tim+vis[m]-1;i++){ 65 ans+=water[i]; 66 }//循环后的 67 printf("%lld",ans); 68 return 0; 69 } 70 } 71 int main(){ 72 gengyf::main(); 73 return 0; 74 } 75
T4 开花
Description
在遥远的火星上,上面的植物非常奇怪,都是长方形的,每个植物用三个数来描述:左边界L、右边界R以及高度H,如下图所示描述一个植物:L=2,R=5和H=4。
每天都有一个新植物长出来,第一天的植物高度为1,后面每天长出的植物比前一天的高1。
当一个新植物长出来的时候,跟其他植物的水平线段相交处会长出一朵小花(前提是之前没有长出花朵),如果线段交于端点,是不会长花的。
下图为样例1的示意图:
给出每天的植物的坐标,计算每天长出多少新花。
Input
第一行包含一个整数N(1<=N<=100000),表示天数。
接下来N行,每行两个整数L和R(1<=L<=R<=100000),表示植物的左右边界。
Output
输出每天长出新植物后增加新花的数量。
Sample Input
输入1:
4
1 4
3 7
1 6
2 6
输入2:
5
1 3
3 5
3 9
2 4
3 8
Sample Output
输出1:
0
1
1
2
输出2:
0
0
0
3
2
Data Constraint
正解
线段树/树状数组
几乎模板
单点查询->单点修改->区间修改
先查询询问区间的端点 L,R 的值
因为长过花的地方不能再长花了,所以把 L和R的值赋成0
最后对本次查询的区间进行区间加,注意端点不算所以处理的区间为[L+1,R-1]
题目来源:COCI2008-2009regional task
code
1 #include2 using namespace std; 3 namespace gengyf{ 4 #define maxn 100010 5 const int inf=1e9; 6 inline int read(){ 7 int f=1,x=0;char s=getchar(); 8 while(!isdigit(s)){ if(s=='-')f=-1;s=getchar();} 9 while(isdigit(s)){x=x*10+s-'0';s=getchar();} 10 return f*x; 11 } 12 int n,a[maxn],b[maxn],c[maxn],maxx; 13 struct Tree{ 14 int l,r,sum,add; 15 #define l(x) t[x].l 16 #define r(x) t[x].r 17 #define sum(x) t[x].sum 18 #define add(x) t[x].add 19 }t[maxn<<2]; 20 void build(int p,int l,int r){ 21 l(p)=l;r(p)=r; 22 if(l==r){ 23 sum(p)=c[l];return ; 24 } 25 int mid=(l+r)>>1; 26 build(p*2,l,mid); 27 build(p*2+1,mid+1,r); 28 sum(p)=sum(p*2)+sum(p*2+1); 29 } 30 void spread(int p){ 31 if(add(p)){ 32 sum(p*2)+=add(p)*(r(p*2)-l(p*2)+1); 33 sum(p*2+1)+=add(p)*(r(p*2+1)-l(p*2+1)+1); 34 add(p*2)+=add(p);add(p*2+1)+=add(p); 35 add(p)=0; 36 } 37 } 38 void change(int p,int l,int r,int d){ 39 if(l(p)>=l&&r(p)<=r){ 40 sum(p)+=d*(r(p)-l(p)+1); 41 add(p)+=d;return ; 42 } 43 spread(p); 44 int mid=(l(p)+r(p))>>1; 45 if(l<=mid) change(p*2,l,r,d); 46 if(r>mid) change(p*2+1,l,r,d); 47 sum(p)=sum(p*2)+sum(p*2+1); 48 } 49 int query(int p,int l,int r){ 50 if(l<=l(p)&&r>=r(p))return sum(p); 51 spread(p); 52 int mid=(l(p)+r(p))>>1; 53 int tmp=0; 54 if(l<=mid) tmp+=query(p*2,l,r); 55 if(r>mid) tmp+=query(p*2+1,l,r); 56 return tmp; 57 } 58 void chang(int p,int x,int k){ 59 if(l(p)==r(p)){ 60 sum(p)=k;return ; 61 } 62 int mid=(l(p)+r(p))>>1; 63 if(x<=mid)chang(p*2,x,k); 64 else chang(p*2+1,x,k); 65 sum(p)=sum(p*2)+sum(p*2+1); 66 } 67 int main(){ 68 n=read(); 69 for(int i=1;i<=n;i++){ 70 a[i]=read();b[i]=read(); 71 maxx=max(maxx,max(a[i],b[i])); 72 } 73 build(1,1,maxx); 74 for(int i=1;i<=n;i++){ 75 printf("%d\n",query(1,a[i],a[i])+query(1,b[i],b[i])); 76 chang(1,a[i],0);chang(1,b[i],0); 77 change(1,a[i]+1,b[i]-1,1); 78 } 79 return 0; 80 } 81 } 82 int main(){ 83 gengyf::main(); 84 return 0; 85 }
8.6 Day 6 听课(数据结构专题)
上午+下午
Splay Treap LCT 替罪羊树 树链剖分
Kd-Tree 可持久化线段树 可持久化Treap
根号算法
分块 块状链表 莫队
例题
HNOI2010 弹飞绵羊
SDOI2014 旅行
SDOI2011 染色
NOI2014 魔法森林
BZOJ2821 作诗
BZOJ2038 小z的袜子
等
8.7 Day 7 考试
T1 直角三角形
Description
二维平面坐标系中有N个点。
从N个点选择3个点,问有多少选法使得这3个点形成直角三角形。
Input
第一行包含一个整数N(3<=N<=1500),表示点数。
接下来N行,每行包含两个用空格隔开的整数表示每个点的坐标,坐标值在-10^9到10^9之间。
每个点位置互不相同。
Output
输出直角三角形的数量。
Sample Input
输入1:
3
4 2
2 1
1 3
输入2:
4
5 0
2 6
8 6
5 7
输入3:
5
-1 1
-1 0
0 0
1 0
1 1
Sample Output
输出1:
1
输出2:
0
输出3:
7
n^3暴力 50pts
正解(之一
枚举每个点为坐标原点,记录每个点现在在第几象限
如果不在第一象限,就把这个点转90度至第一象限
算出其余的点到坐标原点的距离连线的斜率
对斜率进行排序,能和这个点构成直角三角形的点的斜率一定相同
且一定位于相邻象限
统计四个象限内分别有多少个点,再将相邻象限的点数相乘即为答案
复杂度O(n^2logn)
题目来源:COCI2007-2008 contest 2
code(标程
1 #include2 #include 3 #include 4 using namespace std; 5 #define MAX 1500 6 struct ray { 7 long long dx, dy; 8 int quadrant; 9 void rotate90() { 10 long long tmp=dx;dx=dy;dy=-tmp; 11 quadrant=(quadrant+1)%4; 12 } 13 }; 14 bool angle_lt(const ray &A,const ray &B) { return A.dy*B.dx A.dx;} 15 bool angle_eq(const ray &A,const ray &B) { return A.dy*B.dx==B.dy*A.dx;} 16 int n; 17 int x[MAX],y[MAX]; 18 int main(){ 19 scanf("%d",&n); 20 for(int i=0;i "%d%d",&x[i],&y[i]); 21 int ans=0; 22 for(int i=0;i i){ 23 vector rays; 24 for(int j=0;j j){ 25 if(i==j)continue; 26 ray R; 27 R.quadrant=0; 28 R.dx=x[j]-x[i]; 29 R.dy=y[j]-y[i]; 30 while(!(R.dx>0&&R.dy>=0))R.rotate90(); 31 rays.push_back(R); 32 } 33 sort(rays.begin(),rays.end(),angle_lt); 34 int sum[4]; 35 for(int j=0,k;j k){ 36 sum[0]=sum[1]=sum[2]=sum[3]=0; 37 for(k=j;k k) 38 ++sum[rays[k].quadrant]; 39 ans+=sum[0]*sum[1]; 40 ans+=sum[1]*sum[2]; 41 ans+=sum[2]*sum[3]; 42 ans+=sum[3]*sum[0]; 43 } 44 } 45 printf("%d\n",ans); 46 return 0; 47 }