2021-10-13 T2 intersect(正难则反)

T2 intersect

题目描述

游乐园内有这样一个项目:扔出 \(N\) 根木棒,猜这些木棒之间会有多少个交点。
形式化地,我们可以假设木棒的长度近似为无穷,木棒可以互相平行,但是不存在木棒相
互重合或者三条及以上木棒交于同一点的情况。
小 D 想要知道,如果扔出 \(N\) 根木棒,是否有可能出现总交点数为 \(M\) 的情况。
本题多组询问。

数据范围及约定

对于前 \(20\%\) 的数据,\(N\leq 5, M\leq 20\)
对于前 \(50\%\) 的数据,\(N\leq 10, M\leq 100\)
对于前 \(80\%\) 的数据,\(N\leq 50, M\leq 2500, Q\leq 10\)
对于 \(100\%\) 的数据,\(N\leq 500, 0\leq M\leq 10^5, Q\leq 10^5\)

题目分析

对于前 \(20\%\) 的数据,注意到 \(N\) 不超过 \(5\),打表即可通过。
对于前 \(50\%\) 的数据,\(N\) 不超过 \(10\),考虑到可以枚举每一组相互平行的直线数,暴搜即可通过,时间复杂度 \(O(n! + q)\)

void dfs(int cur,int cro)
{
    if(cur == 11) return ;
    for(int i = 1;i <= cur + 1;i++)
    {
        cnt[i]++;
        int tmp = 0;
        for(int j = 1;j <= cur + 1;j++)
            if(i != j) tmp += cnt[j];
        if(cro+tmp > 100)
        {
            cnt[i]--;
            continue;
        }
        flag[cur+1][cro+tmp] = 1;
        dfs(cur+1,cro+tmp);
        cnt[i]--;
    }
}
dfs(0,0);
scanf("%d",&q);
while(q--)
{
    int n,m;
    scanf("%d%d",&n,&m);
    printf("%d\n",flag[n][m]);
}
return 0;

对于前 \(80\%\)​​​​​​ 的数据,考虑 DP,状态很好设计,设 \(f_{i,j}\)​​​​​​ 表示 \(i\)​​​​​​ 条直线有 \(j\)​​​​​​ 个交点是否可行。考虑转移,枚举每组相互平行的直线数量 \(k\)​​​​​​,则有转移方程 \(f_{i,j} = \cup_{k=1}^{i} f_{i-k,j-(i-k)\times k}\)​​​​​ ​ ,初始状态 \(f_{0,0} = 1\)​,时间复杂度 \(O(n^4+q)\)​​。

f[0][0] = true;
for(int i = 1;i <= 50;i++)
    for(int j = 0;j <= i * (i - 1) / 2;j++)
    	for(int k = 1;k <= i;k++)
    		if(j-(i-k)*k >= 0) f[i][j] |= f[i-k][j-(i-k)*k];
while(q--)
{
    int n,m;
    scanf("%d%d",&n,&m);
    printf("%d\n",f[n][m]);
}
return 0;

对于 \(100\%\)​​​​的数据,正难则反,考虑当前的方案与两两相交的方案相差的木棍数量。设 \(g_i\)​​​ 表示与全满方案差 \(i\)​​​ 个交点所需要的木棍数。考虑转移,枚举相差的交点个数为 \(i\)​​​,再枚举当前组内的木棍数量 \(j\)​​​,则多差的交点个数为 \(j\times (j-1)/2\)​​​,则可根据以上性质得出转移方程 \(g_i = min_{i=1}^{N}(j+g_{i-j\times (j-1)/2})\) ​​。则对于方案 \(n\)\(m\),成立需满足当且仅当 \(g_{n\times (n-1)/2-m}\leq n\),注意特判掉超出全满情况。预处理时枚举的复杂度为 \(O(n^3)\),转移的复杂度为 \(O(1)\),则总复杂度为 \(O(n^3+q)\)

memset(g,0x3f,sizeof(g));
g[0] = 0;
for(int i = 0;i <= 500 * 499 / 2;i++)
	for(int j = 1;j <= 500;j++)
	    if(i - j * (j - 1) / 2 >= 0) g[i] = min(g[i],j + g[i-j*(j-1)/2]);
while(q--)
{
	scanf("%d%d",&n,&m);
	if(m > n * (n - 1) / 2)
	{
		printf("0\n");
		continue;
	}
	if(g[n * (n - 1) / 2 - m] <= n) printf("1\n");
	else printf("0\n");
}
return 0;
posted @ 2021-11-22 22:22  一程山雪  阅读(83)  评论(0)    收藏  举报