您的位置:首页 > 其它

SDOI2016 R1 day2 解题报告(bzoj4516,bzoj4517,bzoj4518)

2016-04-12 13:41 399 查看
感言什么的 之后补游记吧

只能说考场没AK,我是傻逼

生成魔咒

题意

给一个字符串,初始为空串,然后往字符串尾部依次添加字符,每添加一个字符询问当前串中本质不同的子串的个数。

数据范围

60%:n<=1000

100%:n<=100000,1<=字符集<=10^9

做过【bzoj3926】[Zjoi20150]诸神眷顾的幻想乡的,会发现这两个题神似,并且这个题还是诸神眷顾的幻想乡的弱化版。

然而数据范围中的字符集太大,貌似SAM不可取?

出题人faebdc给的做法是求反串的SA,然后在SA中的height数组中一个个删除后缀,删除时减去当前后缀的相邻后缀的lcp,再加上新的相邻一对的lcp,更新答案。

SAM的做法就很简单了…边数和点数竟然都是O(n)的,map能过!

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;

typedef long long LL;
const int SZ = 1000010;

struct node{
map<int,node*> ch;
node *par;
int val;
}T[SZ], *last, *root;

int Tcnt = 0;

node* newnode(int x)
{
node *k = T + (Tcnt ++);
k -> val = x;
k -> par = NULL;
k -> ch.clear();
return k;
}

LL ans = 0;

LL get_ans(node *p)
{
return p -> val - p -> par -> val;
}

void insert(int x)
{
node *p = last,*np = newnode(p -> val + 1);
while(p && !p -> ch[x])
p -> ch[x] = np,p = p -> par;
if(!p)
np -> par = root,ans += get_ans(np);
else
{
node *q = p -> ch[x];
if(q -> val == p -> val + 1)
np -> par = q,ans += get_ans(np);
else
{
node *nq = newnode(p -> val + 1);
nq -> ch = q -> ch;

nq -> par = q -> par; ans += get_ans(nq);
np -> par = nq; ans += get_ans(np);
ans -= get_ans(q); q -> par = nq; ans += get_ans(q);

while(p && p -> ch[x] == q)
p -> ch[x] = nq,p = p -> par;
}
}
last = np;
}

void init()
{
root = newnode(0);
last = root;
}

int main()
{
init();
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
int x;
scanf("%d",&x);
insert(x);
printf("%lld\n",ans);
}
return 0;
}
/*
7
1 2 3 3 3 1 2
*/


排列计数

题意

求n的排列中,有多少个排列满足恰好有m个a[i]=i。多组数据。

样例输入

5
1 0
1 1
5 2
100 50
10000 5000


样例输出

0
1
20
578028887
60695423


数据范围

60%:T<=1000,n,m<=1000

70%:T<=500000,n,m<=1000

100%:T<=500000,n,m<=1000000

全排列二十分。状压DP30分(然而这三十分好像都是打表)。出题人给的容斥原理是70分算法,就是求∑Cin(−1)i∗(n−i)!。然而预处理一下就A掉了。

不过我脸好知道错排…答案是Cmn∗f(n−m),其中f是错排公式f(n)=(n−1)∗(f(n−1)+f(n−2))。然后这题我开考半小时就水掉了…

下面是考场代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int SZ = 1000010;
const int mod = 1000000007;

int f[SZ],fac[SZ],c[1010][1010];

void exgcd(LL a,LL b,LL &x,LL &y)
{
if(b == 0)
{
x = 1; y = 0; return ;
}
exgcd(b,a % b,x,y);
LL t = x; x = y; y = t - a / b * y;
}

LL ni(LL a)
{
LL x,y;
exgcd(a,mod,x,y);
return (x % mod + mod) % mod;
}

LL C(int n,int m)
{
return (LL)fac
* ni((LL)fac[n - m] * fac[m] % mod) % mod;
}

void scan(int &n)
{
n = 0;
char a = getchar();
bool flag = 0;
while(a < '0' || a > '9') { if(a == '-') flag = 1; a = getchar(); }
while(a >= '0' && a <= '9') { n = n * 10 + a - '0'; a = getchar(); }
if(flag) n = -n;
}

int main()
{
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
f[0] = 1; f[1] = 0;
for(int i = 2;i <= 1000000;i ++)
f[i] = (LL)(i - 1) * ((LL)f[i - 1] + f[i - 2]) % mod;
fac[0] = fac[1] = 1;
for(int i = 2;i <= 1000000;i ++)
fac[i] = (LL)i * fac[i - 1] % mod;
c[0][0] = 1;
for(int i = 1;i <= 1000;i ++)
{
c[i][0] = 1;
for(int j = 1;j <= i;j ++)
c[i][j] = (LL)(c[i - 1][j - 1] + c[i - 1][j]) % mod;
}

int T;
scan(T);
while(T --)
{
int n,m;
scan(n); scan(m);
if(n < m) puts("0");
else
{
if(n <= 1000 && m <= 1000)
printf("%I64d\n",(LL)c
[m] * f[n - m] % mod);
else
printf("%I64d\n",(LL)C(n,m) * f[n - m] % mod);
}
}
fclose(stdin); fclose(stdout);
return 0;
}
/*
5 1 0 1 1 5 2 100 50 10000 5000*/


征途

题意

把n个数的数列分成m段,每段元素必须连续。每一段的权值是这一段中所有数之和,求最小化方差v。输出v∗m2,这个数必定为整数。

数据范围

60%:1<=m<=n<=100

100%:1<=m<=n<=3000,ai>0,∑ai<=30000

考场上我傻X,写的DP是三维的,40分。

化简一下目标函数。其实是要求每段的平方和最小。

f[i][j]为当前走到第i个,当前是第j段的最小平方和,很容易写出方程:

f[i][j]=min{f[k][j−1]+(s[i]−s[k])2}

这就60分。

发现可以斜率优化。固定j之后,发现满足这个关系:

(fq,j−1+S2q)−(fw,j−1+S2w)Sq−Sw<2Si

其中q优于w且q>w。

然后因为二维的,需要记录上一层的函数值,被这个坑的调了半天……

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;

typedef long long LL;
const int SZ = 3010;
const LL INF = 10000000000000010ll;
LL f[SZ],a[SZ],s[SZ];

LL pf(LL x)
{
return x * x;
}

struct haha{
LL s,x;
}q[SZ];

int t = 0,w = 0;

double xl(haha q,haha w)
{
return (q.s - w.s * 1.0) / (q.x - w.x * 1.0);
}

int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++)
{
scanf("%lld",&a[i]);
s[i] = s[i - 1] + a[i];
}

for(int i = 1;i <= n;i ++)
f[i] = pf(s[i]);
for(int j = 2;j <= m;j ++)
{
t = 1; w = 0;
for(int i = 1;i <= n;i ++)
{
haha np = (haha){f[i] + pf(s[i]),s[i]};
while(t < w && xl(np,q[w]) < xl(q[w],q[w - 1])) w --;
q[++ w] = np;

while(t < w && xl(q[t + 1],q[t]) < 2 * s[i]) t ++;

f[i]=s[i]*s[i]+q[t].s-2*q[t].x*s[i];
}
}
printf("%lld",f
* m - pf(s
));
return 0;
}


最后插一句,这个题骗分在bzoj能A掉…考场上也能90分……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: