补题链接:https://acm.ecnu.edu.cn/contest/173/
已更新题解:A B D E H
正在啃的:C
Problem A
解题思路:
假设取了【l,r】的字符,直接用12345代替对应编号的字符
1 2 3 4 5
12 23 34 45
123 234 345
1234 2345
12345
按照这样一种一种颜色取,即1,2,12,3,23,123...
最后可以全部取到,所以答案就是n*(n+1)/2
代码:
#include<iostream>
#include<string>
#define x (r-l+1)
using namespace std;
int main()
{
int n,q,l,r;
string s;
cin>>n>>q;
cin>>s;
while (q--){
cin>>l>>r;
cout << x*(x+1)/2 << endl;
}
return 0;
}
总结:看到有这么多人做出请果断判断这是签到题,一定有规律!
Problem B
解题思路:
暴力取所有正向的三角形的三个顶点和倒着的三角形的三个顶点,排序后扔一个三个字符的哈希值进set,最后统计个数。
也就是暴力枚举。
代码:
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
const int N = 105;
char a[N][N];
set<int>s;
int main()
{
int n;
while (~scanf("%d",&n)){
s.clear();
n++;
for (int i=1;i<=n;i++) scanf("%s",a[i]+1);
///找出所有正三角形
for (int i=1;i<n;i++){
for (int j=1;j<=i;j++){
int k = 1;
while (i+k<=n){
//printf("i+k=%d j+k=%d\n",i+k,j+k);
int temp[3];
temp[0] = a[i][j]-'a'+1;
temp[1] = a[i+k][j]-'a'+1;
temp[2] = a[i+k][j+k]-'a'+1;
//printf("三个数分别是:%d %d %d\n",temp[0],temp[1],temp[2]);
sort(temp,temp+3);
s.insert(temp[0]*27*27+temp[1]*27+temp[2]);
k++;
}
}
}
//printf("正向的%d个\n",s.size());
///找出所有负三角形
for (int i=n;i>=2;i--){
for (int j=2;j<=i-1;j++){
int k = 1;
while (i-k>=j && j-k>=1){
int temp[3];
temp[0] = a[i][j]-'a'+1;
temp[1] = a[i-k][j]-'a'+1;
temp[2] = a[i-k][j-k]-'a'+1;
//printf("三个数分别是:%d %d %d\n",temp[0],temp[1],temp[2]);
sort(temp,temp+3);
s.insert(temp[0]*27*27+temp[1]*27+temp[2]);
k++;
}
}
}
printf("%d\n",s.size());
}
return 0;
}
总结:想想当时为什么没有做出来呢,还是因为不够冷静下来思考,反而去做看上去做得出实际上自己都不知道错哪的题,这是感性的分析;自己对于第一时间没有思路的题目就觉得很难--放弃,第一眼有思路,虽然思路可能错--往死里做,这是理性的分析。概括的说,菜!
Problem D
解题思路:
如果当前的起点是大于1的,一定可以推出一种先手必胜的策略,自己倒序推就行。
而如果当前起点等于1,那么顺着走下去直到不是1的,开始取那大于1的那堆的那个人必胜。
结论:先取到大于1的那堆的那个人胜利。
特殊处理一下全是1的。
剩下的任务就是判断起点等于1时谁先取到不是1,。
于是构造nt[ ]数组来取得以i为起点时下一次大于1的堆,用队列O(N)复杂度实现。
代码:
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 1e5+5;
queue<int>que;
int nt[N],p[N];
bool flag;
int main()
{
int n;
while (~scanf("%d",&n)){
flag = false;///是否有大于1的堆
memset(nt,0,sizeof nt);
for (int i=1;i<=n;i++){
scanf("%d",p+i);
if (p[i]>1)flag = true;
}
if (!flag){///只有1的堆特殊处理
if (n%2==1) while (n--) printf("First\n");
else while (n--) printf("Second\n");
continue;
}
for (int i=1;i<=n;i++){
if (p[i]>1){
while (!que.empty()){
nt[que.front()] = i;
que.pop();
}
}
else que.push(i);
}
while (!que.empty()){/// 处理最后一个大于1的数后面的1
nt[que.front()] = p[1]==1? nt[1]:1;
que.pop();
}
for (int i=1;i<=n;i++){
if (nt[i]==0) printf("First\n");
else {
int cnt = i>nt[i]? n-i+nt[i]:nt[i]-i; ///cnt表示两点的间距,一个要分两段算,一个直接算
if (cnt%2==0) printf("First\n");
else printf("Second\n");
}
}
}
return 0;
}
总结:碰到的Nim有这么几种类型了:
①n堆,随意取一堆指定数量 -->sg+找sg规律
②按顺序取堆的无限制的 -->找规律,讲究思维
Nim要AC应该需要胆子大敢猜吧。
Problem E
解题思路:
①用欧拉筛预处理一百万的最小质因子
②线段树维护两个东西,一个是区间是否都是1 ud1,一个是乘以最小质数的次数 mtag。
我们知道:一个数乘以最小质数得到的数的最小质数还是本身,所以累乘过程直接做标记了。
③对于每次修改,如果是操作1,直接区间整体mtag++,用到哪儿下放到哪
如果是操作2,当当前区间mtag>0时,直接区间整体mtag--,如果不是,那么可能有些点将被单点暴力修改
④对于每次询问,用快速幂
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define debug(x) printf("----Line %s----\n",#x)
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mid int m = l+r>>1
#define mod 1000000007
using namespace std;
const int N = 1e5+5,M = 1e6+5;
int prime[N],tot;///质数
ll mp[M];///mp[i]表示i的最小质因子
bool vis[M];///欧拉筛用来标记被筛掉的
bool ud1[N<<2];
int mtag[N<<2];
ll v[N];///省点空间,反正没有必要维护区间和
void push_up(int rt)
{
ud1[rt] = ud1[rt<<1] && ud1[rt<<1|1];
}
void push_down(int rt)
{
mtag[rt<<1] += mtag[rt];///加号别忘了-_-||
mtag[rt<<1|1] += mtag[rt];
mtag[rt] = 0;
}
void update(int L,int R,int op,int rt,int l,int r)///操作1,2
{
if (ud1[rt]) return ;
if (op==1 && L<=l && r<=R){
mtag[rt]++;
return ;
}
if (op==2 && L<=l && r<=R && mtag[rt]){
mtag[rt]--;
return ;
}
if (l==r){ ///操作2的暴力修改
v[l] = v[l]/mp[v[l]];
if (v[l]==1) ud1[rt] = true;
return ;
}
if (mtag[rt]) push_down(rt);
mid;
if (L<=m) update(L,R,op,lson);
if (R>m) update(L,R,op,rson);
push_up(rt);
}
void getmp()///欧拉筛
{
int tot = 0;
mp[1] = 1;
for (int i = 2;i<=1000000;i++){
if (!vis[i]){
prime[tot++] = i;
mp[i] = i;
}
for (int j=0;j<tot && i*prime[j]<=1000000;j++){
vis[i*prime[j]] = true;
mp[i*prime[j]] = prime[j];
if (i%prime[j]==0) break;
}
}
}
void build(int rt,int l,int r)///初始化 ud1
{
if (l==r){
if (v[l]==1) ud1[rt] = 1;
return ;
}
mid;
build(lson);
build(rson);
push_up(rt);
}
ll fast_power(ll a,int b)///快速幂
{
ll ans = a;
ll sb = mp[a];
while (b){
if (b%2==1) ans = (ans*sb)%mod;
b>>=1;
sb = sb*sb%mod;
}
return ans;
}
int query(int p,int rt,int l,int r)///得到p点的mtag的下标
{
if (l==r){
return rt;
}
if (mtag[rt]) push_down(rt);
mid;
if (p<=m) return query(p,lson);
else return query(p,rson);
}
int main()
{
getmp();
int n,q,op,l,r,p;
scanf("%d %d",&n,&q);
for (int i=1;i<=n;i++) scanf("%lld",v+i);
build(1,1,n);
while (q--){
scanf("%d",&op);
if (op==1 || op==2){
scanf("%d %d",&l,&r);
update(l,r,op,1,1,n);
}
else {
scanf("%d",&p);
int pos = query(p,1,1,n);
printf("%lld\n",fast_power(v[p],mtag[pos]));
}
}
return 0;
}
总结:眼神死盯着区间为1剪枝,然后暴力修改。第九组数据T成狗。
后来看了大佬的代码发现原来可以区间++,又发现区间--。
感觉自己还是没有掌握线段树的精髓,以后做到线段树题目多问问自己可不可以区间修改。
Problem H
谢谢你让我没爆零,签到题大哥!
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define debug(x) printf("----Line %s----\n",#x)
using namespace std;
const int N = 1e5+5;
int n,m,p,x,y,l,r,ans,cnt;
int main()
{
ll n,a,b,c,d,v;
scanf("%lld %lld %lld %lld %lld",&n,&a,&b,&c,&d);
ll ans = 0;
for (int i=1;i<=n;i++) scanf("%lld",&x),ans+=x;
if (ans>=a && ans>=c) ans = min(ans-b,ans-d);
else if (ans>=a) ans = ans-b;
else if (ans>=c) ans = ans-d;
printf("%lld\n",ans);
return 0;
}