早上去晚了…八点开始的我快九点才到…那个老师说好的八点半集合结果没看见人…逗我呢…
也就这次考得稍好点了,坐看其他几场各位大爷们AK…Orzfqk Orzyzy Orz龙哥 等等等等…
100+60+10……T3数据范围写的20分m<=5,然后第二个点m=6…然后我特判若m<=5则dfs…坑我呢?
题意:
给个字符串,划分不超过K份,从每份中选出字典序最大的子串,然后从这些串中选出字典序最大的串,求问这个串字典序最小是多少,输出这个串。
样例:
2
ababa
ba
对于 20%的数据, 1 <= length(string) <= 100
对于 40%的数据, 1 <= length(string) <= 2000
对于 100%的数据, 1 <= length(string) <= 100000, 1 <= k <= length(string), 所有的字符都是小写字母
考虑后缀数组+二分
二分字典序大小mid,也就是rank。然后开始检查加上这个字母的这份中是否有字典序大于mid的串,若有则把当前字母分到新的一份,没有则继续枚举,最后看看是否能分K份就行了。
考虑一个子区间中最大字典序的子串,可以发现这必定是这个区间的后缀。但每添加一个字母会产生n个后缀,复杂度不能保证。
所以说改为从后往前枚举,然后比较字典序,这样每次添加字母都只会产生一个新的后缀。这样每次就拿这个后缀和sa[mid]比较即可。
然而比较是O(n)的,这样最坏情况下复杂度是O(n^2)的检验。考虑两个字符串比较字典序大小:可以用二分+hash的方法求出LCP,然后比较LCP的下一个字母即可,可以再logn的时间内比较两字符串的大小。
所以总复杂度是 O(nlog2n)
然而我暴力比较的竟然A掉了……
考场代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int SZ = 1000010;
int sa[SZ],rank[SZ],tmp[SZ],k,n,K;
bool cmp(int x,int y)
{
if(rank[x] != rank[y]) return rank[x] < rank[y];
else
{
int a = x + k < n ? rank[x + k] : -1;
int b = y + k < n ? rank[y + k] : -1;
return a < b;
}
}
void get_sa(char s[])
{
for(int i = 0;i <= n;i ++)
{
sa[i] = i;
rank[i] = i == n ? -1: s[i];
}
for(k = 1;k <= n;k <<= 1)
{
sort(sa,sa + 1 + n,cmp);
tmp[sa[0]] = 1;
for(int i = 1;i <= n;i ++)
tmp[sa[i]] = tmp[sa[i - 1]] + cmp(sa[i - 1],sa[i]);
for(int i = 0;i <= n;i ++)
rank[i] = tmp[i];
}
}
char s[SZ];
bool smaller(int l,int r,int x)
{
for(int i = l;i <= r;i ++)
if(s[i] != s[x + i - l])
return s[i] < s[x + i - l];
return true;
}
int cut[SZ][2],t;
bool check(int mid)
{
int x = sa[mid],r = n - 1;
t = 1;
for(int i = n - 1;i >= 0;i --)
{
if(!smaller(i,r,x))
{
cut[t][0] = i + 1; cut[t][1] = r;
// printf("%d %d %d\n",i,r,t);
r = i,t ++;
}
}
cut[t][0] = 0; cut[t][1] = r;
// printf("%d %d %d\n\n",t,mid,x);
return t <= K;
}
void div()
{
int l = -1,r = n;
while(r - l > 1)
{
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid;
}
check(r);
// printf("%d %d\n",l,r);
}
bool smaller(int l,int r,int x,int y)
{
for(int i = l;i <= r;i ++)
{
if(x + i - l > y) return false;
if(s[i] != s[x + i - l])
return s[i] < s[x + i - l];
}
return true;
}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%d%s",&K,s);
n = strlen(s);
get_sa(s);
// for(int i = 0;i <= n;i ++)
// printf("%s\n",s + sa[i]); puts("");
div();
int ansl = n + 1,ansr = n + 1;
// for(int i = 1;i <= t;i ++)
// printf("%d %d\n",cut[i][0],cut[i][1]);
for(int i = 1;i <= t;i ++)
{
for(int j = cut[i][0];j <= cut[i][1];j ++)
{
if(!smaller(j,cut[i][1],ansl,ansr))
{
// printf("%d %d %d %d\n",j,cut[i][1],ansl,ansr);
ansl = j; ansr = cut[i][1];
}
}
}
for(int i = ansl;i <= ansr;i ++)
printf("%c",s[i]);
return 0;
}
/*
0
1 a
2 aba
3 ababa
4 ba
5 baba
3
bababbb
*/
题意:
你买股票,鸡汁的你预测到了未来n天某支股票的价格,记为序列 {ai} 。在第i天,必须选择买入一股或者卖出一股,都是 ai 元。卖出一股时手头上必须有至少一股股票,求最大获利。
4
1 3 2 4
4
4
1 2 3 4
4
对于 10%的数据, 1 <= n <= 200
对于 30%的数据, 1 <= n <= 3000
对于 100%的数据, 1 <= n, a_i <= 200000
考虑30分DP:
dp[i][j]表示第i天手持j个股票的最大获利,转移非常好写:
dp[i][j]=max(dp[i−1][j+1]+a[i],dp[i−1][j−1]−a[i])
30分稳拿,我60,骗了30 233
正解:
考虑贪心。因为价钱都是正的,所以到最后肯定尽量让持有的股票最少,尽量让买的次数和卖的次数相同,尽量花少的买入,按贵的卖出。
维护一个卖出的集合S,对于奇数天考虑买入,偶数次卖出。先啥也不管全卖了,但奇数天必须买入否则就没法卖了,于是我们把卖出集合中最便宜的换成买入就可以了,实现的话用个堆就行了。
考完后重写的代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
priority_queue<int> q;
int main()
{
freopen("stock.in","r",stdin);
freopen("stock.out","w",stdout);
int n;
scanf("%d",&n);
LL ans = 0;
for(int i = 1;i <= n;i ++)
{
int x;
scanf("%d",&x);
q.push(-x);
ans += x;
if(i & 1)
{
int t = -q.top(); q.pop();
ans -= t * 2;
}
}
printf("%lld",ans);
return 0;
}
题意:
给一个长度为2*n+1的序列,每个位置的数的范围为[0,m],然后给每个位置同时加x,满足x∈[L,R],问有多少种不同的初始序列使其加上任意的x的异或和为0。x不同但初始序列相同算作同一种方案。
第一行四个整数 n, m, L 和 R,意义如题目描述所示
一行一个整数,表示可能的幸福度方案的数目,结果对 1000000007 取模
1 3 1 3
12
对于 20%的数据, 1 <= n, m <= 5, 1 <= L <= R <= 10
对于 40%的数据, 1 <= n, m <= 100, 1 <= L <= R <= 100
对于 70%的数据, 1 <= n, m <= 500, 1 <= L <= R <= 500
对于 100%的数据, 1 <= n, m <= 1000, 1 <= L <= R <= 1000
对于样例,共有以下几种分配幸福度的方案:
(0,1,2)
(0,2,1)
(0,2,3)
(0,3,2)
(1,0,2)
(1,2,0)
(2,0,1)
(2,0,3)
(2,1,0)
(2,3,0)
(3,0,2)
(3,2,0)
20暴搜……还我十分……
先证明一个东西 [L,R]区间内至多只有一个数x能成为答案。
若存在一个x合法,考虑x的某个为1的位,相当于给数列中所有数的这一位取反。因为是2*n+1个,长度为奇数个,若存在一个x+d(d!=0)合法,则考虑d的某个为1的位,相当于把那一位取反,但因为是长度是奇数所以这样会导致异或值必定不为0。
谁会想到这个啊喂
然后就可以这样dp:
dp[i][j]为选到第i个异或为j的方案数,转移很显然
dp[i][j]+=dp[i-1][j^k],其中k为当前位的值
这样是40分
好像还有就是f[L][k]=\sum f[L/2][j] * f[L/2][l],把上面的递推改成分治了,然后容易理解后面的做法…
貌似再往后需要一些奇怪的东西…毒瘤题毁我一生…
FWT什么的,线段树什么的……就这样吧 我不会了233
贴个考场代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int SZ = 1000010;
const int mod = 1000000007;
int n,m,l,r;
namespace one{
const int SZ = 110;
int ans;
int num[SZ];
bool check()
{
for(int d = l;d <= r;d ++)
{
int x = 0;
for(int i = 1;i <= n;i ++)
x ^= num[i] + d;
if(!x) return true;
}
return false;
}
void dfs(int pos)
{
if(pos == n + 1)
{
if(check()) ans ++;
return ;
}
for(int i = 0;i <= m;i ++)
{
num[pos] = i;
dfs(pos + 1);
num[pos] = 0;
}
}
int solve()
{
dfs(1);
return ans;
}
}
namespace two{
const int SZ = 3010;
int dp[SZ][SZ];
int DP(int x,int y)
{
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
for(int i = 1;i <= n;i ++)
{
for(int j = 0;j <= 256;j ++)
{
for(int k = x;k <= y;k ++)
{
dp[i][j] = (dp[i][j] + dp[i - 1][j ^ k]) % mod;
}
}
}
return dp[n][0];
}
int solve()
{
return 0;
}
}
int main()
{
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&l,&r);
n = n * 2 + 1;
if(m <= 5)
{
printf("%d\n",one :: solve());
}
else
{
printf("%d\n",two :: solve());
}
return 0;
}
/* 第i位,异或得j,当前是k dp[i][j] += dp[i - 1][j ^ k] */