2019.11.16考试分析

2019.11.16考试分析

考试时间:3h
考试总成绩:90
考试名次:26(名次)/37(总人数)

题目1:

刚看到的时候有点头大,自己尝试画了一下样例6,然后非常的麻烦,三角形也很难找,有点自闭,先去的做的后面两道题,最后再回来做的第一题,想尝试找规律但是一开始的思路就错掉了一错再错下去也没找出规律只能自己手推打表,很难模拟只模拟出9组数组来。
预估成绩:17.5(期望值)
实际成绩:10
考试用时:1h
出错原因:无
预防措施:无
题目分析
第一个样例6个点可以构成1个这样的三角形,所以可以类比出来任意6个点都可以构成这么一个三角形,所以n个点能够构成多少个这样的三角形,就是在n个里面取出6个的组合数。

  • 暴力求阶乘50分
    组合数的公式是这样的:

\[C_n^m = \dfrac{m!}{n!(n-m)!} \]

直接暴力算就可以了,但是有一个很难受的地方就是这道题没有模数,所以这样暴力求阶乘很容易爆long long
所以只能拿50分

  • 递归求组合数
    阶乘求组合数爆掉的原因是因为超出了long long,因为\(C_n^m\)本身没有超出long long的范围,只是在除出正确的结果之前先爆掉了long long,所以可以用递归求组合数的方法吼!因为这样是直接求\(C_n^m\)的值,所以不会爆掉。
    虽然不会爆long long了,但是,很可怕的一件事出现了,那就是超时了,因为这个递归求组合数复杂度太高了,会超时,只能拿70分。
  • 递归求组合数+记忆化
    但是,超时也是有原因的——重复计算多次组合数
    这就是100*100,一共才10000中组合数,一个只计算一遍都不会超时,所以出现超时情况只能是重复计算,所以记忆化就用上啦!
    轻轻松松A掉,跑的飞快。
  • 因为m是一定的,而且阶乘暴力求解只在n大的时候才会爆,为什么要提到这个呢?原因就是n!是1到n乘起来,除以了1到m乘起来的数和1到(n-m)乘起来的数,上面的1到n乘起来的数可以和下面某一个越掉一部分,比如和1到m乘起来的数一约就变为了m+1到n乘起来的数。
    但是这两个怎么选择呢?就用到了刚才提到的,在n大的时候才会爆掉,所以和1到(n-m)乘起来的数约掉显然是更优的,毕竟m!阶乘就是个720太小了,不如前者更优。
    所以式子就可以化为

\[C_n^m = \dfrac{(n - 5) * (n - 4) * (n - 3) * (n - 2) * (n - 1) * n}{720} \]

轻轻松松!

正解
50分暴力求阶乘

#include<iostream>
#include<cstdio>
#define int long long

using namespace std;

int read()
{
    int sum = 0,fg = 1;
    char c = getchar();
    while(c < '0' || c > '9')
    {
        if(c == '-')fg = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9')
    {
        sum = sum * 10 + c - '0';
        c = getchar();
    }
    return sum * fg;
}

int jc(int x)
{
    int ans = 1;
    for(register int i = 1;i <= x;++ i)
        ans *= i;
    return ans;
}

int C(int n,int m)
{
    return jc(n) / (jc(m) * (jc(n - m)));
}

signed main()
{
    int n = read();
    cout << C(n,6) << endl;
}

70分递归求组合数

#include<iostream>
#include<cstdio>

using namespace std;

int read()
{
    int sum = 0,fg = 1;
    char c = getchar();
    while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
    while(c >= '0' && c <= '9'){sum = sum * 10 + c - '0';c = getchar();}
    return sum * fg;
}

int C(int n,int m)
{
    if(n == m)return 1;
    if(m == 0)return 1;
    return C(n - 1,m) + C(n - 1,m - 1);
}

int main()
{
    int n = read();
    if(n <= 5)
    {
        cout << 0 << endl;
        return 0; 
    }
    cout << C(n,6) << endl;
}

100分递归+记忆化代码

#include<iostream>
#include<cstdio>

using namespace std;
const int Max = 101;
int c[Max][Max];
int read()
{
    int sum = 0,fg = 1;
    char c = getchar();
    while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
    while(c >= '0' && c <= '9'){sum = sum * 10 + c - '0';c = getchar();}
    return sum * fg;
}

int C(int n,int m)
{
    if(c[n][m] != 0)return c[n][m];
    if(n == m)return c[n][m] = 1;
    if(m == 0)return c[n][m] = 1;
    return c[n][m] = C(n - 1,m) + C(n - 1,m - 1);
}

int main()
{
    int n = read();
    if(n <= 5)
    {
        cout << 0 << endl;
        return 0; 
    }
    cout << C(n,6) << endl;
}

100分组合数公式改进法

#include<iostream>
#include<cstdio>
#define int long long

using namespace std;
const int Max = 101;
int c[Max][Max];
int read()
{
    int sum = 0,fg = 1;
    char c = getchar();
    while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
    while(c >= '0' && c <= '9'){sum = sum * 10 + c - '0';c = getchar();}
    return sum * fg;
}

int C(int n,int m)
{
    if(c[n][m] != 0)return c[n][m];
    if(n == m)return c[n][m] = 1;
    if(m == 0)return c[n][m] = 1;
    return c[n][m] = C(n - 1,m) + C(n - 1,m - 1);
}

signed main()
{
    int n = read();
    if(n <= 5)
    {
        cout << 0 << endl;
        return 0; 
    }
    int ans = 1;
    for(register int i = n - 5;i <= n;++ i)
        ans *= i;
    cout << ans / 720 << endl;
    return 0;
}

题目2:

看到这道题想到了01背包,因为需要判断这个是不是必须的可以通过不用这个面值的来看一下如果没有这个面值的能不能拼出m来,如果能够拼出来那这个面额就不是必须的,如果不能那就是必须的,跑n遍01背包,复杂度也就刚刚卡过去60分。
预估成绩:40-60
实际成绩:60
考试用时:1h
出错原因:无
预防措施:无
题目分析
跑一遍01背包记录1-m每个面额能够被拼成的次数,然后枚举1到n每一个面额,如果bb[x - a[i]] == bb[x]说明从bb[x - a[i]]到b[x]只有一种方案,那么a[i]就是必须的。
正解

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 100005
#define M 100005
using namespace std;
int ai[N],fi[M]={0},gi[M],ans[N]={0};
bool di[N]={false};
int main(){
    freopen("coin.in","r",stdin);
    freopen("coin.out","w",stdout);
    
    int n,x,i,j,k,cnt=0;
    scanf("%d%d",&n,&x);
    for (i=1;i<=n;++i){
        scanf("%d",&ai[i]);
        di[ai[i]]=true;
    }
    fi[0]=1;
    for (i=1;i<=n;++i)
        for (j=x;j>=ai[i];--j)
            fi[j]+=fi[j-ai[i]];
    for (i=1;i<N;++i){
        if (!di[i]) continue;
        memset(gi,0,sizeof(gi));
        for (j=0;j<i;++j)
            gi[j]=fi[j];
        for (;j<=x;++j)
            gi[j]=fi[j]-gi[j-i];
        if (!gi[x]) ans[++ans[0]]=i;
    }
    printf("%d\n",ans[0]);
    for (i=1;i<=ans[0];++i)
        printf("%d ",ans[i]);
    printf("\n");
}

题目3:

看着题目貌似简单易懂,但是却是一道不可做题,输出-1会有分的吗?不现实,不过里面有20%的数据范围每个点入度都小于等于1,那么就是一颗树或者是链的东西,不管是什么只要入度小于等于1的话那就有一个很显然的性质,两点之间只有一条路径,只需要将找出这道路径,然后每条路改加或者减去一个值之后最接近0的数是多少就好了。
预估成绩:20
实际成绩:20
考试用时:1h
出错原因:无
预防措施:无
题目分析

正解

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#define pa pair<int,int>
#define inf 1000000000
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int T,n,m,cnt,ans,mid;
int last[105],q[105],d[105];
bool mark[105],con[105];
struct data{int to,next,v;}e[200005];
void insert(int u,int v,int w)
{
	e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;e[cnt].v=w;
}
bool dfs(int x)
{
	mark[x]=1;
	for(int i=last[x];i;i=e[i].next)
		if(d[x]+e[i].v+mid<d[e[i].to]&&con[e[i].to])
		{
			if(mark[e[i].to])return 1;
			d[e[i].to]=d[x]+e[i].v+mid;
			if(dfs(e[i].to))return 1;
		}
	mark[x]=0;
	return 0;
}
void spfa()
{
	for(int i=1;i<=n;i++)d[i]=inf;
	int head=0,tail=1;
	q[0]=1;mark[1]=1;d[1]=0;
	while(head!=tail)
	{
		int now=q[head];head++;if(head==100)head=0;
		for(int i=last[now];i;i=e[i].next)
			if(d[now]+e[i].v+mid<d[e[i].to]&&con[e[i].to])
			{
				d[e[i].to]=d[now]+e[i].v+mid;
				if(!mark[e[i].to])
				{
					mark[e[i].to]=1;
					q[tail]=e[i].to;tail++;
					if(tail==100)tail=0;
				}
			}
		mark[now]=0;
	}
}
void dfscon(int x)
{
	mark[x]=1;
	for(int i=last[x];i;i=e[i].next)
		if(!mark[e[i].to])
			dfscon(e[i].to);
}
bool jud()
{
	for(int i=1;i<=n;i++)
		if(con[i])
		{
			for(int j=1;j<=n;j++)d[j]=inf;
			memset(mark,0,sizeof(mark));
			if(dfs(i))return 0;
		}
	spfa();
	if(d[n]>=0&&d[n]!=inf)return 1;
	return 0;
}
int main()
{
	freopen("home.in","r",stdin);
	freopen("home.out","w",stdout);
	T=read();
	while(T--)
	{
		memset(con,1,sizeof(con));
		memset(last,0,sizeof(last));
		cnt=0;ans=-1;
		n=read();m=read();
		for(int i=1;i<=m;i++)
		{
			int u=read(),v=read(),w=read();
			insert(u,v,w);
		}
		memset(mark,0,sizeof(mark));
		dfscon(1);
		for(int i=1;i<=n;i++)if(!mark[i])con[i]=0;
		for(int i=1;i<=n;i++)
			if(con[i])
			{
				memset(mark,0,sizeof(mark));
				dfscon(i);
				if(!mark[n])con[i]=0;
			}
		int l=-100000,r=100000;
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(jud())ans=d[n],r=mid-1;
			else l=mid+1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

考试过程总结:
这次考试T2和T3都拿到了该拿的暴力,也是不吃亏的,只是在T1上面,大部分的人都是A掉了, 我却只有10分,在这道题目上面被落下了很多分,也是这次考试失败的原因。不过T1的规律是真的不好找,毕竟这种类型的题目做的不多,也没有在找规律的时候想太多。

posted @ 2019-11-13 16:08  acioi  阅读(263)  评论(0编辑  收藏  举报