2019.8.1模拟B组
- 游戏 (Standard IO)
Time Limits: 2000 ms Memory Limits: 262144 KB Detailed Limits
Goto ProblemSet
Description
Alice和Bob在玩一个游戏,游戏是在一个NN的矩阵上进行的,每个格子上都有
一个正整数。当轮到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
100%数据满足
1<=N<=1000
保证每一行或每一列的和不会超过210^9
思路:
简单博弈论,不需要用到sg函数。博弈论类题目与动态规划题目有一些类似,都是建立在递推上的刷表,对题目状态的遍历成一个有向无环图。对于这一类问题,我们需要把握起始的状态,转移的条件。我们将所有的状态分为必胜点和必败点两种。对于这个题目来说,我们可以发现只有一行,只有一列是初始的状态。我们按照游戏的反方向进行递推。我们预处理出这些状态。然后开始递推。每一个状态只能转移到它正下方一格和正右方一格的位置。转移的条件是当前行是偶数,并且转移前是必败。我们处理出了所有必胜的点,其余的都是必败的点。这里需要思考一个逻辑,能转移到必败点是必胜点的充要条件。
找到初态
转移的方式
转移的条件
#include<bits/stdc++.h>
using namespace std;
int t,n;
int a[1010][1010];
int k[1010][1010];
int sum[1010][1010];
int main(){
scanf("%d",&t);
while(t--){
memset(k,0,sizeof(k));
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
}
}
k[1][1]=(!(a[1][1]%2));
for(int i=1;i<=n;i++){
if((sum[1][i]-sum[1][0])%2==0) k[1][i]=1;
}
for(int i=1;i<=n;i++){
if((sum[i][1]-sum[0][1])%2==0) k[i][1]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==1 && j==1) continue;
if(k[i][j]) continue;
if((i>1 && k[i-1][j]==0 && (sum[i][j]-sum[i-1][j])%2==0) || (j>1 && k[i][j-1]==0 && (sum[i][j]-sum[i][j-1])%2==0)){
k[i][j]=1;
}
else k[i][j]=0;
}
}
if(k[n][n]) cout<<"W"<<endl;
else cout<<"L"<<endl;
}
}
Description
棋盘是由许多个六边形构成的,共有5种不同的六边形编号为1到5,棋盘的生成规
则如下:
1.从中心的一个六边形开始,逆时针向外生成一个个六边形。
.对于刚生成的一个六边形,我们要确定它的种类,它的种类必须满足与已生成的相
邻的六边形不同。
3.如果有多个种类可以选,我们选择出现次数最少的种类。
4.情况3下还有多个种类可以选,我们选择数字编号最小的。
现在要你求第N个生成的六边形的编号?
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
思路:
模拟。将六边形放到熟悉的邻接矩阵中,找到规律。我们可以将题目中的图形进行分层。对于找规律的题目我们要找到序列中的“循环节”,这样更能看清楚本质。
#include<bits/stdc++.h>
using namespace std;
int f[1100][1100];
struct node{
int key;
int num;
}p[10];
bool cmp(const node &a,const node &b){
if(a.num==b.num) return a.key<b.key;
return a.num<b.num;
}
int ans[1000100],k,n;
int now;
void in(int x,int y){
sort(p+1,p+6,cmp);
bool bk[10];
memset(bk,0,sizeof(bk));
bk[f[x-1][y]]=bk[f[x+1][y]]=bk[f[x][y-1]]=bk[f[x][y+1]]=bk[f[x-1][y-1]]=bk[f[x+1][y+1]]=1;
for(int i=1;i<=5;i++){
if(bk[p[i].key]==0){
f[x][y]=p[i].key;
p[i].num++;
ans[now]=p[i].key;
break;
}
}
now++;
}
int main(){
int x=250,y=250;
for(int i=1;i<=5;i++)
p[i].key=i;
p[1].num=1;
ans[1]=1;
f[x][y]=1;
now=2;
k=1;
while(now<10005){
x++,y++;
for(int i=1;i<=k;i++) x--,in(x,y);
for(int i=1;i<=k;i++) x--,y--,in(x,y);
for(int i=1;i<=k;i++) y--,in(x,y);
for(int i=1;i<=k;i++) x++,in(x,y);
for(int i=1;i<=k;i++) x++,y++,in(x,y);
for(int i=1;i<=k;i++) y++,in(x,y);
k++;
}
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
printf("%d\n",ans[x]);
}
}
- 数列 (Standard IO)
Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits
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
思路:
dp。cnt[i]表示以i为结尾的子串。确定了每一个i后(阶段),我们只需要找到与他能形成符合题意的开头j。我们将序列前缀和。ij能构成合法序列当且仅当sum[i],sum[j-1]在%k意义下同余。我们将每个sum的余数用一个桶装起来,f[j],
if(f([i]==f[j])cnt[i]++; if(f([i]==f[j]) cnt[i]++;if(f([i]==f[j])cnt[i]++;
易知,这样进行转移是既不重复也不遗漏的。
#include<bits/stdc++.h>
#define int long long
typedef long long ll;
using namespace std;
int t;
int k,n;
int a[50010],sum[50010],f[1000010];
int ok[500010],cnt[50010];
signed main(){
scanf("%lld",&t);
while(t--){
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
memset(f,0,sizeof(f));
memset(ok,0,sizeof(ok));
memset(cnt,0,sizeof(cnt));
scanf("%lld%lld",&k,&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
ok[i]=sum[i]%k;
}
ll ans=0;
f[0]=1;
for(int i=1;i<=n;i++){
ans+=f[ok[i]];
f[ok[i]]++;
}
printf("%lld\n",ans);
}
}
反思
看到一个题,突然卡住,懵B的情况很多。这种情况应该第一想法是,这个题考的是什么知识点。去往这个方向去想。比如说第二题,在没有思路的情况下发现这个题目没有考察算法。这时观察数据范围应该想到模拟。
不要去套模板。有的题目不如我所想的那么复杂。从题目本身出发而不是去想自己以前做过类似的题。