CF 895C-Square Subsets

本文探讨了一个组合数学问题,即从一个包含重复元素的集合中选取若干元素,使其乘积为完全平方数的方案数。通过将相同元素合并并利用组合数与动态规划的方法,实现了高效算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意简述

给你一个有n个元素的可重集(n<=1e5,元素大小<=70),从这个集合中选出若干元素使得这些元素的积为完全平方数,求方案数mod 1e9+7

做法

注意到元素非常的小,70以内的质数只有19个,那么我们想到状压dp,f[i][j]表示到第i个数,前面有质因子的集合是j的方案数,转移是O(218218),状态是219n219∗n的,会挂掉。
但是我们可以把所有相同的元素整到一起,那么我们只要考虑这个元素取奇数个或者偶数个,离散化之后num[i]表示i这个元素的的数量,我们以sum1[i]表示i这个元素选奇数个的方案数,那么sum1[i]=C(num[i],1)+C(num[i],3)++C(num[i],num[i]1)sum1[i]=C(num[i],1)+C(num[i],3)+…+C(num[i],num[i]−1)我们这里假定num[i]是个奇数,然后sum2[i]=C(num[i],0)+C(num[i],2)++C(num[i],num[i])sum2[i]=C(num[i],0)+C(num[i],2)+…+C(num[i],num[i])
考虑sigma(num[i])==n,所以我们总复杂度是O(nlog(n))O(n∗log(n))
好了,预处理好这两个东西后我们进行dp
以k表示第i个元素的因子中出现奇数次的集合
f[i][j]=f[i1][j]sum2[i]+f[i1][jf[i][j]=f[i−1][j]∗sum2[i]+f[i−1][j xor k]sum1k]∗sum1
最后答案为f[cnt][0],其中cnt表示的就是离散化完的元素个数,复杂度7021970∗219

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<iostream>
#include<cmath>
#define LL long long
#define INF (2139062143)
#define N (1000001)
using namespace std;
int n,cnt,d;
int a[N];
LL sum1[N],sum2[N],ny[N],jc[N],nyjc[N];
int f[71][524300];
int ss[N]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67};
const int P=1000000007;
struct node{
    int data,num;
}b[N];
LL C(LL a,LL b){
    return jc[a]*nyjc[b]%P*nyjc[a-b]%P;
}
template <typename T> void read(T&t) {
    t=0;
    bool fl=true;
    char p=getchar();
    while (!isdigit(p)) {
        if (p=='-') fl=false;
        p=getchar();
    }
    do {
        (t*=10)+=p-48;p=getchar();
    }while (isdigit(p));
    if (!fl) t=-t;
}
int main(){
    read(n);
    for (int i=1;i<=n;i++) read(a[i]);
    sort(a+1,a+n+1);
    for (int i=1;i<=n;i++){
        if (i==1||a[i]!=a[i-1]){
            b[++cnt].data=a[i];
            b[cnt].num=1;
        }
        else b[cnt].num++;
    }
    jc[0]=1;
    for (int i=1;i<=100000;i++) jc[i]=jc[i-1]*i%P;
    ny[0]=ny[1]=nyjc[1]=nyjc[0]=1;
    for (int i=2;i<=100000;i++) ny[i]=(-(P/i)*ny[P%i]%P+P)%P,nyjc[i]=nyjc[i-1]*ny[i]%P;

    for (int i=1;i<=cnt;i++){
        for (int j=0;j<=b[i].num;j++){
            if (j&1) sum1[i]=(sum1[i]+C(b[i].num,j))%P;
            else sum2[i]=(sum2[i]+C(b[i].num,j))%P;
        }
    }

    f[0][0]=1;
    for (int i=1;i<=cnt;i++){
        int k=0;
        for (int j=0;j<=18;j++){
            int tt=b[i].data,qq=0;
            while (tt%ss[j]==0){
                qq++;
                tt/=ss[j];
            } 
            if (qq%2==1) k+=1<<j;
        }
        for (int j=0;j<=(1<<19)-1;j++){
            f[i][j]=(f[i][j]+1ll*f[i-1][j]*sum2[i]%P+1ll*f[i-1][j^k]*sum1[i])%P;
        }
    }
    printf("%d",f[cnt][0]-1);
    return 0;
}
### C语言实现最小生成树算法 (Prim 和 Kruska) 以下是基于C语言编写的最小生成树算法的示例代码,分别实现了 **Prim** 和 **Kruskal** 算法。这些代码满足题目中的基本要求,并提供了详细的注释以便理解。 #### Prim 算法 Prim 算法的核心是从某个起点出发逐步构建一棵最小生成树,每次选择连接已访问节点和未访问节点之间的最短边[^2]。 ```c #include <stdio.h> #define MAX 100 #define INF 999999 // 辅助函数:寻找下一个加入MST的顶点 int findMinKey(int key[], int mstSet[], int V) { int min = INF, min_index; for (int v = 0; v < V; ++v) if (!mstSet[v] && key[v] < min) min = key[v], min_index = v; return min_index; } void primMST(int graph[][MAX], int V) { int parent[V]; // 存储构造的MST int key[V]; // 关键值用于挑选最小权重边 int mstSet[V]; // 记录是否已经加入了MST // 初始化所有的key为INF,parent为-1 for (int i = 0; i < V; ++i) key[i] = INF, mstSet[i] = 0; key[0] = 0; // 设置第一个顶点的关键值为0 parent[0] = -1; // 第一个节点无父节点 for (int count = 0; count < V - 1; ++count) { int u = findMinKey(key, mstSet, V); mstSet[u] = 1; // 更新相邻顶点的key值 for (int v = 0; v < V; ++v) if (graph[u][v] && !mstSet[v] && graph[u][v] < key[v]) parent[v] = u, key[v] = graph[u][v]; } printf("Edge \tWeight\n"); for (int i = 1; i < V; ++i) printf("%d - %d \t%d \n", parent[i], i, graph[parent[i]][i]); } ``` --- #### Kruskal 算法 Kruskal 算法通过不断选取全局范围内权值最小的边并将其加入到生成树中,直到形成一颗连通的树为止[^1]。 ```c #include <stdio.h> #include <stdlib.h> typedef struct Edge { int src, dest, weight; } Edge; typedef struct Graph { int V, E; Edge* edge; } Graph; Graph* createGraph(int V, int E) { Graph* graph = malloc(sizeof(Graph)); graph->V = V; graph->E = E; graph->edge = malloc(E * sizeof(Edge)); return graph; } // 查找集合代表元 int find_set(int subsets[], int i) { if (subsets[i] == -1) return i; return find_set(subsets, subsets[i]); } // 合并两个子集 void union_set(int subsets[], int x, int y) { int xset = find_set(subsets, x); int yset = find_set(subsets, y); subsets[xset] = yset; } // 主体逻辑 void kruskalMST(Graph* graph) { int V = graph->V; Edge result[V]; int e = 0; int i = 0; qsort(graph->edge, graph->E, sizeof(graph->edge[0]), compare); int subset[V]; for (int v = 0; v < V; ++v) subset[v] = -1; while (e < V - 1 && i < graph->E) { Edge next_edge = graph->edge[i++]; int x = find_set(subset, next_edge.src); int y = find_set(subset, next_edge.dest); if (x != y) { result[e++] = next_edge; union_set(subset, x, y); } } printf("Following are the edges in the constructed MST:\n"); long total_weight = 0; for (i = 0; i < e; ++i){ printf("%d -- %d == %d\n", result[i].src, result[i].dest, result[i].weight); total_weight += result[i].weight; } printf("\nThe Total Weight of Minimum Spanning Tree is :%ld\n",total_weight); } // 排序辅助函数 int compare(const void* a, const void* b) { Edge* a1 = (Edge*)a; Edge* b1 = (Edge*)b; return a1->weight > b1->weight ? 1 : -1; } ``` --- ### 综合程序设计 为了使两种算法能够在一个程序中共存,可以编写如下菜单驱动的功能模块: ```c #include <stdio.h> void menu() { printf("\nMenu Options:"); printf("\n1. Run Prim's Algorithm."); printf("\n2. Run Kruskal's Algorithm."); printf("\n3. Exit.\nYour choice: "); } int main() { int option; do { menu(); scanf("%d", &option); switch(option) { case 1: // Call function to run Prim algorithm. break; case 2: // Call function to run Kruskal algorithm. break; case 3: printf("Exiting program...\n"); break; default: printf("Invalid Option! Please try again.\n"); } } while (option != 3); return 0; } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值