Wannafly挑战赛14

源地址:here
A

在三维空间中,平面 x = 0, y = 0, z = 0,以及平面 x + y + z = K 围成了一个三棱锥。
整天与整数打交道的小明希望知道这个三棱锥内、上整点的数目。

他觉得数量可能很多,所以答案需要对给定的 M 取模。

思路1:

对于一个三菱锥来说,其是由k+1个直角三角形组成,仅仅考虑其整数点

对于其中一个直角三角形来说,其中的整数点为(i^2 + i) /2

于是三菱锥中的整数点为: 1/2*∑(i^2 + i) (1<=i<=k+1)

由平方和公式:


于是∑i^2 = (k+1)(k+2)(2k+3) / 6

∑i = (k+1)(k+2) / 2

1/2∑(i^2 + i) (1<=i<=k+1) = 1/12 * (k+1)(k+2)(2k+6)

= (k+1) * (k+2) * (k+3) / 6


思路2:

题中的意思就是找到 满足 a + b + c <= n的元组个数:

而 a + b + c <= n 可以转换为 a + b + c + d = n的四元组个数;

利用隔板法,将 k 个 1 分成 4 份,

其中允许a,b,c,d为空,于是我们人为的给a,b,c,d都加上1,于是就满足了隔板法的定义

于是 k + 4个1中有 k + 3 个空位,插入三个板子将其分成4份

就是 C(k+3,3) = (k+1)(k+2)(k+3) / 6


#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
ll mod;
int main()
{
ll k;
int caset;scanf("%d",&caset);
while(caset--)
{
scanf("%lld%lld",&k,&mod);
printf("%lld\n",(k+3)(k+2) % (6*mod) (k+1) % (6*mod) / 6);
}
return 0;
}

C

题意:

给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,

输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。

题解:

这里我们分析两个解法:

解法1: Tarjan缩点

通过Tarjan算法进行强连通分量的染色.

对于两个强连通分量a,b来说 有三种情况

1.要么a,b之间没有边相连 ///那么这个子集必须包括 a 的 点 和 b 的点

2.要么 a 能到 b ///这个最小子集只需要有a的点即可

3.要么 b 能到 a ///这个最小的子集只需要有b的点即可

以上的操作可以通过入度数组来维护

注意从强联通分量选择点时,也应当选择最小的点,最后需要排序

复杂度: O(n+m)

代码:

#include<bits/stdc++.h> 
using namespace std;
const int MAXN = 1e5+10; /// 点数
const int MAXM = 1e5+10; /// 边数
typedef struct node{
int to,next;
}Edge;
Edge edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; /// Belong数组的值是1~scc
int Index,top; /// Low 和 DFN 的遍历顺序
int scc; /// 强连通分量的个数
bool Instack[MAXN];
vector<int> id[MAXN];
void addedge(int u,int v) {
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void Tarjan(int u) {
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u];~i;i = edge[i].next) {
v = edge[i].to;
if(!DFN[v]) {
Tarjan(v);
if(Low[u] > Low[v]) Low[u] = Low[v];
}
else if(Instack[v] && Low[u] > DFN[v])
Low[u] = DFN[v];
}
if(Low[u] == DFN[u]) {
scc++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = scc;
id[scc].push_back(v);
}
while( v != u);
}
}
void solve(int N) {
memset(DFN,0,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
for(int i=0;i<MAXN;i++) id[i].clear();
Index = scc = top = 0;
for(int i=1;i<=N;i++) if(!DFN[i]){
Tarjan(i);
}
}
int u[MAXM],v[MAXM],in[MAXN];
vector<int> ans;
void init() {
tot = 0;
memset(head,-1,sizeof(head));
memset(in,0,sizeof(in));
ans.clear();

}
int main()
{
int n,m;
while(~scanf(“%d%d”,&n,&m))
{
init();
for(int i=1;i<=m;i++) {
scanf(“%d%d”,&u[i],&v[i]);
addedge(u[i],v[i]);
}
solve(n);
for(int i=1;i<=m;i++) {
if(Belong[u[i]] == Belong[v[i]]) continue;
in[Belong[v[i]]]++;
}
for(int i=1;i<=scc;i++) sort(id[i].begin(),id[i].end());
for(int i=1;i<=scc;i++) if(!in[i]) ans.push_back(id[i][0]);
sort(ans.begin(),ans.end());
printf(“%d\n”,(int)ans.size());
for(int i=0;i<(int)ans.size();i++)
printf(“%d%c”,ans[i],” \n”[i+1 == (int)ans.size()]);
}
return 0;
}

解法2:

比赛结束后,无意中看到的一种思路

首先我们可以标记所有的点的入度

对于入度为0的点u进行深搜,把所有能到达u的点的父节点标记为u,个人感觉变成了1个以根节点u的树

遍历完后,图中还有一些没有遍历到的节点,这些节点是自成一个环的节点集

对于这些节点进行深搜,因为搜索顺序是从小到大,所以,最小的节点作为了父节点

最后,所有的父节点fnt[i] == i的节点就是我们要的子集

#include<bits/stdc++.h> 
using namespace std;
const int maxn = 1e5+10;
vector<int> way[maxn],ans;
int in[maxn];
int fnt[maxn];
void init() {
ans.clear();
memset(in,0,sizeof(in));
memset(fnt,0,sizeof(fnt));
for(int i=0;i<maxn;i++) way[i].clear();
}
void dfs(int x,int fa) {
if(fnt[x] == fa) return;
fnt[x] = fa;
for(int i=0;i&lt;way[x].size();i++) {
    dfs(way[x][i],fa);
}

}
int main()
{
int n,m;
while(~scanf(“%d%d”,&n,&m))
{
init();
while(m–) {
int u,v;
scanf(“%d%d”,&u,&v);
way[u].push_back(v);
in[v]++;
}
for(int i=1;i<=n;i++) if(in[i] == 0) {
dfs(i,i);
}
for(int i=1;i<=n;i++) if(fnt[i] == 0) {
dfs(i,i);
}
for(int i=1;i<=n;i++) if(fnt[i] == i) {
ans.push_back(i);
}
int len = ans.size();
printf(“%d\n”,len);
for(int i=0;i<len-1;i++) printf(“%d “,ans[i]);
printf(“%d\n”,ans[len-1]);
}
return 0;
}



猜你喜欢

转载自blog.csdn.net/huatian5/article/details/80033889