月赛传送门

http://acm.neu.edu.cn/hustoj/contest.php?cid=1066

月赛已经结束十天了。。。才把题目补完真是大失误。。。

茅巨巨四天前就补完了,,,总结写得比我详细,附上传送门

http://blog.csdn.net/morejarphone/article/details/50659795

这次月赛感觉题目都不是很难(嗯,没有什么难的数学题或者牛逼的数据结构题目)

只有AEF卡我了,所以今天就只说这三道题目

A题

给你一个x,判断是否有n(n满足在十进制下每一位都为1),满足n可以整除x的,如果有,输出最小的n

样例输入

1

2

3

样例输出

1

-1

111

因为x的范围是10W的,所以一开始以为是数论,怎么想怎么不明白,感觉没有什么规律,提交个优化的暴力枚举上去果然WA,然后三个小时都没做,先去搞其他的题目了(此时已经有10+人A掉了。。。),后来转换思路,发现没必要真的去得到n,只要循环着让x乘一个数字加上余项后末尾为1就好了,当余项每一位都为1时停止循环,记录循环了几次。。。就是一个模拟。。。如果循环次数超过1W,就假设不存在(这里不知道怎么去证明),,,于是A掉了。。

E题

给你一个n*n的邻接矩阵,求所有点两两之间最短路长度平方的和(如果不存在最短路,则长度为n)

这个上来就可以想到floyd,但是是n^3的复杂度,交了一发果然T。。。

后来想到可以类似B题的写bfs,图的话转换成邻接表存储,感觉优化了很多,实际上复杂度应该还是n^3的。。。果然T掉

 #include<cstdio>
#include<iostream>
using namespace std;
const int maxn=;
int a[][];
char ch[];
//int f[1001][1001];
int lin[],cnt=;
struct str
{
int next;
int y;
}e[maxn*maxn];
int q[maxn*maxn];
void insert(int x,int y)
{
e[++cnt].next=lin[x];
lin[x]=cnt;
e[cnt].y=y;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
int head=,tail=-;
for(int i=;i<n;i++)
{
scanf("%s",ch);
for(int j=;j<n;j++)
{
a[i][j]=ch[j]-'';
if(i==j)a[i][j]=;
if(a[i][j])
{
insert(i,j);
q[++tail]=i*n+j;
}
if(i!=j&&!a[i][j])a[i][j]=;
}
}
while(head<=tail)
{
int x=q[head]/n,y=q[head]%n;
for(int i=lin[y];i;i=e[i].next)
{
int u=e[i].y;
if(a[x][u]==)
{
a[x][u]=a[x][y]+;
q[++tail]=x*n+u;
} }
head++;
}
long long ans=;
for(int i=;i<n;i++)
{
for(int j=;j<n;j++)
{
if(a[i][j]==)a[i][j]=n;
//printf("%d ",a[i][j]); ans+=a[i][j]*a[i][j];
}
//printf("\n");
}
printf("%lld\n",ans);
}
return ;
}

T的代码

结束后膜拜了了刘巨巨的代码(所谓的dfs外加瞎几把剪枝),感觉很有道理的样子,他写的也是bfs,但是他依旧用邻接矩阵存储,每次bfs固定起点(比如从1号点出发),算出从某个点到所有点的最短路,每次都从小到大去走,于是保证了如果之前访问过编号小的,之后就没必要再次访问,于是他写了一个vis数组,记录被访问过的节点,还有一个head指针,记录下次寻找路径的起点,这就形成了一个简单的“最优化剪枝”,

 #include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int tu[][];
bool vis[];
int head,tail;
int q[];
int lu[];
int n;
int main()
{
long long ans;
while(scanf("%d",&n)!=EOF)
{
ans=;
for(int i=;i<=n;i++)
{
char tem[];
scanf("%s",tem);
for(int j=;j<=n;j++)
{
if(tem[j-]=='')
tu[i][j]=false;
else tu[i][j]=true;
}
}
for(int i=;i<=n;i++)
{
memset(vis,false,sizeof(vis));
memset(lu,0x3f,sizeof(lu));
head=;
lu[i]=;
int tou=,wei=;
//-----------------------------------dfs?
int no=i;
q[++wei]=no;
vis[no]=true;
while(tou<wei)
{
int x;
tou++;
x=q[tou];
for(int ii=head;ii<=n;ii++)
if(!vis[ii]&&tu[x][ii])
{
lu[ii]=lu[x]+;
vis[ii]=true;
q[++wei]=ii;
}
while(vis[head])//这里是关键!!!
head++;
}
//---------------------------------
for(int j=;j<=n;j++)
if(lu[j]<=n)
ans+=lu[j]*lu[j];
else
ans+=n*n;
}
printf("%lld\n",ans);
}
return ;
}

刘巨巨的代码

思路很好,但是实际上是有bug的,如果1号节点被孤立出来,入度为0(出度随意),那么这个剪枝就废了,于是想出了正解,可以用list存储尚未访问的点,每次扫描list判断是否可以拓展,访问一个节点删除一个节点,于是复杂度降到n^2

 #include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=;
int tu[][];
bool vis[];
int head,tail;
int q[];
int lu[];
int n;
struct str
{
int next;
int rev;
}e[];
void ini_e()
{
for(int i=;i<;i++)
e[i].next=i+;
e[].next=;
for(int i=;i<maxn;i++)
e[i].rev=i-;
e[].rev=;
}
void del_e(int xx)
{
int nextt=e[xx].next;
int revv=e[xx].rev;
e[nextt].rev=revv;
e[revv].next=nextt;
}
int main()
{
long long ans;
while(scanf("%d",&n)!=EOF)
{
ans=;
for(int i=;i<=n;i++)
{
char tem[];
scanf("%s",tem);
for(int j=;j<=n;j++)
{
if(tem[j-]=='')
tu[i][j]=false;
else tu[i][j]=true;
}
}
for(int i=;i<=n;i++)
{
memset(vis,false,sizeof(vis));
memset(lu,0x3f,sizeof(lu));
lu[i]=;
int tou=,wei=;
//-----------------------------------dfs?bfs!
ini_e();
int no=i;
q[++wei]=no;
vis[no]=true;
while(tou<wei)
{
int x;
tou++;
x=q[tou];
for(int ii=e[].next;ii;ii=e[ii].next)
if(!vis[ii]&&tu[x][ii])
{
lu[ii]=lu[x]+;
vis[ii]=true;
del_e(ii);
q[++wei]=ii;
}
}
//---------------------------------
for(int j=;j<=n;j++)
if(lu[j]<=n)
ans+=lu[j]*lu[j];
else
ans+=n*n;
}
printf("%lld\n",ans);
}
return ;
}

最终代码

实际上这题目是有标程的,写的很迷,看不懂。。。还在研究(不过据说出题人想卡刘巨巨代码失败了,反而卡死了标程,偶莫西罗依),谁看懂的话麻烦教导我。。。

 #include <map>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
typedef unsigned long long ull;
const ull one=;
ull lowbit(ull n)
{
return n&(-n);
}
const int perbit=;
map<ull,int> mp;
struct Bitset
{
vector<ull> vec;
ull bitmap;
void init(int n)
{
vec.clear();
bitmap=;
while(n>=perbit)
{
vec.push_back(-);
n-=perbit;
bitmap<<=;
}
if(n) vec.push_back((one<<n)-),bitmap<<=;
bitmap-=;
}
void remove(int i)
{
vec[i/perbit]^=(one<<(i%perbit));
if(vec[i/perbit]==)
bitmap^=(one<<(i/perbit));
}
int getNext()
{
if(bitmap==) return -;
int cur=mp[lowbit(bitmap)];
int ans=cur*perbit+mp[lowbit(vec[cur])];
remove(ans);
return ans;
}
};
const int maxn=1e3+;
char g[maxn][maxn];
ull dis[maxn];
ull bfs(int s,int n)
{
memset(dis,-,sizeof(dis));
queue<int> que;
que.push(s);
dis[s]=;
Bitset bit;
bit.init(n);
bit.remove(s);
while(!que.empty())
{
Bitset cur=bit;
int u=que.front();que.pop();
int v;
while((v=cur.getNext())!=-)
{
if(g[u][v]=='')
{
bit.remove(v);
dis[v]=dis[u]+;
que.push(v);
}
}
}
ull ans=;
for(int i=;i<n;i++)
if(dis[i]==-)
ans+=n*n;
else
ans+=dis[i]*dis[i];
return ans;
}
int main()
{
for(int i=;i<;i++)
mp[one<<i]=i;
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=;i<n;i++)
scanf("%s",g[i]);
ull ans=;
for(int i=;i<n;i++)
ans+=bfs(i,n);
printf("%llu\n",ans);
}
return ;
}

E题迷之标程

F题

题面写的超级迷,要不是后来在讨论版有了详细解释真的是无从下手

题目就是说给你一个n,然后给你n个数字,保证这n个数字是各不相同的且都大于0小于n+1(这意味着这n个数字是1……n的一个排列),然后问你有多少个长度为n的排列,在从小到大sort后保证该排列中数字的相对顺和给定排列中数字顺序相同

(可能我解释的也有点儿迷,我们来看样例)

样例输入

3

2 1 3

样例输出

15

注释

/*

I'd like to interpret the second test case. 
3
2 1 3
As we know, there are 27 kinds of permutation of {1,2,3} . 
They are
{1,1,1}{1,1,2}{1,1,3}{1,2,1}{1,2.2}{1,2,3}{1,3,1}{1,3,2}{1,3,3}
{2,1,1}{2,1,2}{2,1,3}{2,2,1}{2,2,2}{2,2,3}{2,3,1}{2,3,2}{2,3,3}
{3,1,1}{3,1,2}{3,1,3}{3,2,1}{3,2,2}{3,2,3}{3,3,1}{3,3,2}{3,3,3}
Now, define a new way to sort (given in the test case):2 1 3
which means for each permutation above put all the '2' first,
put all the '1' after all the '2', and then put all the '3' after all the '1'.
After that, if this kind permutation is in ascending order.It can be count!
So {1,1,1}{1,1,3}{1,3,1}{1,3,3} {2,2,2}{2,2,3}{2,3,2}{2,3,3}
{3,1,1}{3,1,3}{3,2,2}{3,2,3}{3,3,1}{3,3,2}{3,3,3} THIS 15 kinds can be counted.

*/

(以上摘自官方解释)

{1,3,1}为什么可以?因为sort后是{1,1,3},1在3的前面,和给定的排列是一样的

{2,1,1}为什么不行,因为sort后是{1,1,2},1在2的前面,和给定的排列相左。

题目理解了我们开始想思路。

其实初步的思路很简单,很容易看出来和给定排列的上升子序列还有排列组合容斥原理有关,不需要枚举具体的排列(废话),具体关系又是什么呢?

/*

F Sort It

首先我们考虑这样一件事情,怎样的一个串是p sortable的。

(1)串中数字的位置是无关紧要的,重要的是这个串是由哪些数字组成的

(2)串中组成的数字必定是p中的一个上升子序列

因此这个题目的答案就是g[i]*f[i] (for i = 1 to n),g[i]是长度为i的上升子序列的个数,f[i]是用i个数字组成长度为n的串(其中每个数至少出现一次)的方法数。

对于g[i],我们考虑这样一个dp,dp[i][j],代表以第i个数为结尾,并且长度为j的上升子序列有多少个,一个朴素的实现是O(n^3)的,显然要优化,自己去想= =

对于f[i],容斥即可。

*/

(以上摘自谢学长题解)

谢学长帮我理顺了思路,让我不至于一头雾水,但是接下来任务依旧不简单,需要求解g数组和f数组

--------------------------------------------------------------------------------------------------------------------

先说f数组

一开始我也以为是容斥原理,或者是高级的组合数学,推了很久推不出来,后来我去百度了一下,莫名其妙的百度到了“第二类斯特林数”,然后为递推方法求解f数组提供了思路

(PS:第二类Stirling数实际上是集合的一个拆分,表示将n个不同的元素拆分成m个集合的方案数,第二类其实是和我们有区别的,他的集合之间没有区别,我们的有)

stirl[i][j]表示i种数字组成j个数字的排列,且每种数字至少出现一次的方法数

那么stirl[i][j]=(stirl[i-1][j-1]+stirl[i-1][j-1])*i;

我们考虑最后一位的数字,如果这个数字不同于前面所有的数字,那么前面的数字就是i-1个数排j-1位;否则就是i个数排j-1位。因为最后一个数字可以是任意的i个数之一,所以要乘i。

于是,stirl[i][n]就是题解中提到的f[i],求解复杂度为n^2,可以

------------------------------------------------------------------------------------------------------------------------

好,接下来是求解g数组

朴素的dp方法是n^3的,原理简单,f[i][j]以第j个数字结尾的长度为i的上升子序列的个数

f[i][j]=sum(f[i-1][k])(k<j且第k位的数字小于第j位的数字)

这个复杂度是n^3的,2000的数据范围受不了

可以优化么?答案是可以的,最内层的循环(就是累加的那个)可以用树状数组压缩一下

(每次求和的花销由n降至logn,复杂度可接受)

计算该层的时候,把上一层的数据压到树状数组里面

于是f[i][j]=sum(a[j]);add(a[j],f[i-1][j]);

于是 g[i]=sum(f[i][1……n])。

--------------------------------------------------------------------------------------------------------------------------

不得不承认,这个题解写的好迷。。。直接看代码吧。。。发着烧补的。。。能过掉自己都感觉神奇

(PS茅巨巨内层的写法和我的不太一样。。。)

 #include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
const ll modd=;
const int maxn=;
ll stirl[maxn][maxn];//i种数
ll f[maxn][maxn];
ll bit[maxn];
inline int lowbit(int x)
{
return x&(-x);
}
int n=;
int a[maxn];
ll read(int x)
{
ll sum=;
while(x)
{
sum+=bit[x];
x-=lowbit(x);
}
sum%=modd;
return sum;
}
void add(int x,ll num)
{
while(x<=n)
{
bit[x]+=num;
bit[x]%=modd;
x+=lowbit(x);
}
}
int main()
{
//freopen("test.out","w",stdout);
for(int i=;i<maxn;i++)
{
stirl[][i]=;
}
for(int i=;i<maxn;i++)
{
for(int j=;j<maxn;j++)
{
stirl[i][j]=(stirl[i-][j-]+stirl[i][j-])*i;
stirl[i][j]%=modd;
}
}
while(~scanf("%d",&n))
{
for(int i=;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(f,,sizeof(f));
ll ans=;
for(int i=;i<=n;i++)
{
f[][i]=;
}
ans=n;
for(int i=;i<=n;i++)
{
memset(bit,,sizeof(bit));
ll sum=;
for(int j=;j<=n;j++)
{
ll temp=read(a[j]);
f[i][j]=temp;
//printf("%lld ",temp);
sum+=temp;
add(a[j],f[i-][j]);
}
//printf("\n");
sum%=modd;
sum*=stirl[i][n];
sum%=modd;
ans+=sum;
ans%=modd;
}
/*
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
printf("%lld ",stirl[i][j]);
}
printf("\n");
}
*/
printf("%lld\n",ans);
}
return ;
} /*
7
7 2 1 4 6 3 5
*/

F的最终代码

这个代码我一开始怎么提交怎么错,但是感觉我的stirl和f数组都没有写错,当我学习了对拍后,发现n超过7我就开始出错,于是打表,发现stirl[1][7]==0;

然后就发现stirl数组第一行循环的上界,我把maxn错写成n。。。。然而n的初值我定义为6.。。

另外一个错误是sum函数和add函数,我认为在树状数组中数字不会超过int,但是实际上会的,注意随时取余。

------------------------------------------------------------------------------------------------------------------------------

05-28 22:21