A题。。
题意:给你一个字符串,问其中出现了k次的子串有多少个。
比赛的时候就感觉是后缀数组,先是题目看错了,以为是大于或等于k次的,然后想出来了和题解差不多的方法,然后lcw好心的提醒了我题目看错了,然后就没想了。。
先求出height数组,从当前i开始扫k个,
区间为【i,i+k-1】如果lcp为3,表示有3个字符串至少出现了k次(比如lcp对应字符串是abc,那三个字符串为a,ab,abc,为什么不考虑bc,c?,因为之后会出现bc为前缀的,c为前缀的),然后需要减去出现k+1次的,区间是【i,i+k】和【i-1,i+k-1】,然后多减去了出现k+2次,加上【i-1,i+k】的lcp就行了,这里注意边界,以及k=1的情况
<当时就是没有想到减去。。>
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
#define ll long long
const int maxn = 1e5+10;
int vis[maxn],q[maxn];
char str[maxn];
struct Suffix
{
int s[maxn];
int SA[maxn],ranked[maxn],height[maxn];
int x[maxn],y[maxn],buc[maxn],len,m;
void init(char *str)
{
memset(SA,0,sizeof(SA));
memset(s,0,sizeof(s));
memset(ranked,0,sizeof(ranked));
memset(height,0,sizeof(height));
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
memset(buc,0,sizeof(buc));
len=strlen(str);
for(int i=0;i<len;i++) s[i]=str[i]-'a'+1;
m=35;//
}
void GetSA()
{
for (int i = 0; i < m; i++)
buc[i] = 0; // buc 是一个桶
for (int i = 0; i < len; i++) buc[x[i] = s[i]]++;
for (int i = 1; i < m; i++)
buc[i] += buc[i - 1];
for (int i = len - 1; i >= 0; i--)
SA[--buc[x[i]]] = i;
for (int k = 1; k <= len; k <<= 1) { // k 倍增
int p = 0;
//对第二关键字排序,y[i]:第i大的第二关键字是谁
// 后缀 len - k 及之后的所有后缀第二关键字最小。为0
for (int i = len - 1; i >= len - k; i--)
y[p++] = i;
for (int i = 0; i < len; i++)
if (SA[i] >= k)
y[p++] = SA[i] - k;
//总体来排个序,求出SA
for (int i = 0; i < m; i++) buc[i] = 0;
for (int i = 0; i < len; i++)
buc[x[y[i]]]++;
for (int i = 1; i < m; i++)
buc[i] += buc[i - 1];
for (int i = len - 1; i >= 0; i--)
SA[--buc[x[y[i]]]] = y[i];
swap(x, y);
p = 1; x[SA[0]] = 0;
// 重新计算每个一元的名次。则x数组里存的是总体的顺序
for (int i = 1; i < len; i++) {
if (y[SA[i - 1]] == y[SA[i]] && y[SA[i - 1] + k] == y[SA[i] + k])
x[SA[i]] = p - 1;
else x[SA[i]] = p++;
}
if (p >= len) break; // 每个后缀的名次已经完全不同,不需要继续倍增
m = p; // 更新名次的最大值。
}
}
void Getheight()
{
for(int i=0;i<len;i++)
ranked[SA[i]]=i;
int k=0;
for(int i=0;i<len;i++)
{
if(ranked[i]==0) {height[0]=0;continue;}
if(k) k--;
int j=SA[ranked[i]-1];
while(s[i+k]==s[j+k]&&i+k<len&&j+k<len) k++;
height[ranked[i]]=k;
}
}
};
Suffix SS;
int dp[maxn][20];
void RMQ(int len)
{
for(int i=1;i<len;i++) dp[i][0]=SS.height[i];
for(int i=1;(1<<i)<=len;i++)
{
for(int j=1;j+(1<<i)-1<=len;j++)
{
dp[j][i]=min(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);
}
}
}
int len;
int ass(int i,int j)
{
if(i<0) return 0;
if(j>=len) return 0;
if(i==j) return len-SS.SA[i];
int k=0;
i++;
while((1<<(k+1))<=j-i+1) k++;
if(k==0) return dp[i][k];
return min(dp[i][k],dp[j-(1<<k)+1][k]);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(dp,0,sizeof(dp));
int k;
scanf("%d",&k);
scanf("%s",str);
SS.init(str);
SS.GetSA();
SS.Getheight();
len=strlen(str);
RMQ(len);
ll ans=0;
for(int i=0;i+k-1<len;i++)
{
int t1=ass(i,i+k-1);
int t2=-ass(i,i+k);
int t3=-ass(i-1,i+k-1);
int t4=ass(i-1,i+k);
ans+=t1+t2+t3+t4;
}
printf("%I64d\n",ans);
}
return 0;
}
B题:ans=k+(m-k)*k
D题:dp
E题:ans=F(2k+3)-1
F(1)+F(2)+…+F[n]=F[n+2]-1;
F题:gems gems gems
题意:给n张牌,每张牌都有一个值,A和B轮流从左往右取牌,前一个人取了k张,后一个人只能取k或者k+1张。A先取,第一次只能取1或者2张。A要是他们的差值尽可能大,B要使他们的差值尽可能地小
woc,这个题怎么又理解错了题意:(
(比赛中) 从左往右取理解成了,A,B在取牌的时候,要求取的牌的下标是从左往右连续的,A和B开始取牌的位子不定。。比如1,2,10,4 ; (我理解的最佳方案是)A取2,10,则B就不能取了。。。如果是我理解的这样的话,是不是贼复杂,这么复杂肯定不是dp。。博弈的话,必胜必败状态又找不出来==
忘掉之前xiaoshuo的
我们来看看这个题的正确打开方式
从左往右取,比如1,2,3,4,5 A取了1,2,B只能从3开始取2张或者3张
解法:dp。
dp[i][j][k]:第i个人,从第j张开始取,前一轮取了k张
i=0,表示A
i=1,表示B
sum:表示前缀和
状态转移:
dp[0][j][k]=max(dp[1][j+k][k],dp[1][j+k+1][k+1]+sum[j+k])+sum[j+k-1]-sum[j-1];
dp[1][j][k]=min(dp[0][j+k][k],dp[1][j+k+1][k+1]-sum[i+j])-sum[j+k-1]+sum[j-1];
因为这道题的空间比较小,int数组大概只能开到1e6;
首先k,1+2+….+k<=n,所以k不超过200;对当前第j个数,最多只会受到j+k+1的影响。用滚动数组。滚动数组还只能用&,如果用%会超时==,用&的话,inf就取255,这样&起来就和%一样的意思了
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdio>
using namespace std;
const int maxn = 20005;
#define inf 255
int sum[maxn];
int dp[2][260][210];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(sum,0,sizeof(sum));
int n,a;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
sum[i]=sum[i-1]+a;
}
int maxk=(int)sqrt(2*n)+1;
memset(dp,0,sizeof(dp));
for(int i=n;i>=1;i--)
{
for(int k=1;k<=maxk&&k+i-1<=n;k++)
{
dp[0][i&inf][k]=dp[1][(i+k)&inf][k]+sum[i+k-1]-sum[i-1];
cout<<dp[0][i&inf][k]<<endl;
dp[1][i&inf][k]=dp[0][(i+k)&inf][k]+sum[i-1]-sum[i+k-1];
cout<<dp[1][i&inf][k]<<endl;
if(i+k<=n)
{
dp[0][i&inf][k]=max(dp[0][i&inf][k],dp[1][(i+k+1)&inf][k+1]+sum[i+k]-sum[i-1]);
cout<<dp[0][i&inf][k]<<endl;
dp[1][i&inf][k]=min(dp[1][i&inf][k],dp[0][(i+k+1)&inf][k+1]+sum[i-1]-sum[i+k]);
cout<<dp[1][i&inf][k]<<endl;
}
}
}
printf("%d\n",dp[0][1][1]);
}
return 0;
}
H题:transaction transaction transaction
队友过的。。我还没补。。据说学长直接dfs出来了?
J:ping ping ping
当时队友用树剖怼,果断超时。
首先想到LCA吧。。
lca按深度从大到小排个序,然后对于当前的lca,我们看它所在的那条路径上有没有被破坏掉的点,如果没有就去掉当前lca.
关于如何确定有没有被破坏的点,要么就是从两点往上走,看有没有经过被破坏掉的点。要么从当前lca往所有子树标记,如果子树被标记了,则不用继续标记,最后看u,v两点有没有被标记。要么从两点往根找,一直到找到自己的lca,如果中途没找到了已经被破坏掉的lca,则去掉当前lca。
L:题目看懂了,模拟一下。