序言(~v~)
本蒟蒻由于太弱只能开数论坑了qwq。
本专题可能不会涉及那么多比较难的数论问题qwq
一般都是简单数学(主要是思维)还有简单贪心(毕竟我太菜了)qwq
【14/20】
(小声咕咕:都是luogu的题)
[1/20](数论)luogu【p2671】求和(NOIP2015pjT3)
挺好的数论推导题。首先看着数据范围就是O(N)解(qwq)但是我蒟蒻啊推了一天
要注意的是,对于”xyz 是整数, x<y<z,y-x=z-y,x<y<z;y−x=z−y“这个条件,我们会发现x+z=2y
即x+z一定为偶数---------x于z要么同奇要么同偶
对于每一种颜色,我们可以得到O(N^2)解法。(当然有很多n^2的解法qwq)
对于第i个元素,我们假设【c为其颜色,p为其位置,n为其数字】,每遇到一次就一次累加:
color[c]=(pi+pj)*(ni+nj) (1<=j<=k,其中k表示迄今为止,该颜色奇/偶的元素的个数(不包含当前i元素))
展开来,得到color[c]=pi*ni+pi*nj+pj*ni+pj*nj 一共k次累加
我们可以发现答案可以表示成为 pi*ni*k+pi*(n1+n2+...+nk)+(p1+p2+...+pk)*ni+(p1*n1+p2*n2+...+pk*pk)
很高兴!我们可以用一个数组记录每种颜色的(n1+n2+...+nk),(p1+p2+...+pk),以及(p1*n1+p2*n2+...+pk*pk)
出现了!O(N)的效率!
【特别声明:一定要分奇偶!!!】
2018-08-01
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const int orzhjw=100010; ll a,b,c,d,ans=0,m=10007; ll number[orzhjw]={0},color[orzhjw]={0}; ll sumn1[orzhjw]={0},sump1[orzhjw]={0},howmany1[orzhjw]={0},sumq1[orzhjw]={0}; ll sumn2[orzhjw]={0},sump2[orzhjw]={0},howmany2[orzhjw]={0},sumq2[orzhjw]={0}; inline ll get(ll &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%d ",x); } int main(){ get(a);get(b); for(ll i=1;i<=a;i++)get(number[i]); for(ll i=1;i<=a;i++){ ll x;get(x); if(i&1){ color[x]+=howmany1[x]*i*number[i]%m+i*sumn1[x]%m+number[i]*sump1[x]%m+sumq1[x]%m; sumn1[x]+=number[i];sump1[x]+=i; howmany1[x]++;sumq1[x]+=number[i]*i; } else{ color[x]+=howmany2[x]*i*number[i]%m+i*sumn2[x]%m+number[i]*sump2[x]%m+sumq2[x]%m; sumn2[x]+=number[i];sump2[x]+=i; howmany2[x]++;sumq2[x]+=number[i]*i; } color[x]%=m; } for(ll i=1;i<=b;i++){ //put(color[i]); ans=(ans+color[i])%m; } put(ans);turn; } //872
[2/20](数论)luogu【P2119】魔法阵(NOIP2016pjT4)
蒟蒻我又来搬题解了。。。。
80%
首先我们看一下数据:
你会发现n*n*n可以过80分。。。。
(有好吃的为什么不吃呢?)
显然,对于一个xa,那这个xa可以和其他x组成的魔法阵个数为nb*nc*nd(乘法原理)(xb,xc,xd同理)。
于是又根据小学生思维,对于一个四元等式,确定3个可以得到第4个。所以我们可以把所有n弄出来,然后排序,去重。
由于已经排序而且去重,所以可以直接从前面枚举xb,xc,xd然后判断xa存不存在以及是否满足要求。
100%
100分怎么办?
我们设xd-xc=k,xc-xb=p,xb-xa=m;
由于我们可以从条件里得到
xb-xa=2(xd-xc),所以m=2*k,那就意味着有k就有m。
以及3*2(xd-xc)<xc-xb,所以可以把m表示成6*k<p.
整理一下,我们有:
xb=xa+2*k;
xc=xd-k;
xb+6*k<xc;
在k确定的情况下:
1.对于一个确定的xd,可以得到xc,以及xb的最大值xb=xc-6*k-1(即xd-7*k-1),有了xb的最大值也有了xa的最大值xa=xd-9*k-1;
2.对于一个确定的xa,可以得到xb,以及xc的最小值xc=xb+6*k+1(即xa+8*k+1),有了xc的最大值也有了xd的最大值xd=xc+k;
好了记住它们。(qwq)
现在我们回到那个80分的暴力,我们可以发现当我们在枚举xb的时候,存在的xb的数值的范围应该是(2,xd-7*k-1);
然后第一次是f(2,3),第二次是f(2,4),第三次是f(2,5)........最后一次是f(2,xd-7*k-1);
可以观察到每一次都重复查找了上一次的个数!!!
由于k>=1,所以c的最小值为xa+8*k+1=1+8+1=10;
那当我们枚举xc的时候,你会发现xc最小时所求出来的xb也是最小值(xb=xc-6*k-1=10-6-1=3),当xc最大时xb也是最大。
又因为xc是等差递增,所以xb也是等差递增,而且效果和暴力差不多。
所以我们可以用前缀和优化一下,用一个sum记录前面一共有多少不同的合法的xa和xb的情况,根据乘法原理,可以得到C[xc]=D[xd]*sum,D[xd]=C[xc]*sum;
而对于A,B的答案更新,同样可以求出范围,然后根据乘法原理累加一波。
(注意sum清0)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const int orzhjw=40010; ll a,b,c,d,num=0; ll whh[orzhjw]={0},old[orzhjw]={0}; ll A[orzhjw]={0},B[orzhjw]={0},C[orzhjw]={0},D[orzhjw]={0}; int what[orzhjw]={0}; ll lyf[orzhjw]={0}; inline ll get(ll &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%lld ",x); } int main(){ get(b);get(a); for(int i=1;i<=a;i++){ get(old[i]);what[i]=old[i];lyf[old[i]]++; } for(int k=1;k*9<=b;k++){ ll summ=0; for(int xd=9*k+2;xd<=b;xd++){ ll xc=xd-k,xa,xb; xa=xd-9*k-1; xb=xa+k+k; summ+=lyf[xa]*lyf[xb]; C[xc]+=lyf[xd]*summ; D[xd]+=lyf[xc]*summ; } summ=0; for(int xa=b-9*k-1;xa>=1;xa--){ ll xb=xa+k+k,xc,xd; xc=xb+6*k+1; xd=xb+7*k+1; summ+=lyf[xc]*lyf[xd]; A[xa]+=lyf[xb]*summ; B[xb]+=lyf[xa]*summ; } } for(int i=1;i<=a;i++){ int x=what[i]; put(A[x]);put(B[x]);put(C[x]);put(D[x]);turn; } return 0; }
【3/20】(贪心)luogu【P2672】推销员
60%
可以DP一波:
DP[i][j]表示前i个以i结尾选择j个的最大值,它的答案等于i前面的j个点权的最大和,我们用f[i][j]表示;
那么
DP[i][j]=f[i-1][j-1]+a[i]+2*s[i];
f[i][j]=max(f[i-1][j],f[i-1][j-1]+a[i]);
那么——很愉快地TLE了qwq
100%
我们会发现无论怎样,当前答案都是之前的答案加一个点。根据DP的思想,当前的答案可以是上一状态加上一个最大的值,使得答案最大。
然后我们需要分类讨论:
- 我们假设新加的点为第x个点
- 当x在当前答案的末尾(即被选答案的距离最大)左边,那么答案就加上a[i]
- 当x在当前答案的末尾右边,那么答案就 ans=ans-s[tail]+a[x]+s[i]+s[i](减去之前的最大距离 再加上 新加的权值)
- 因为tail和新加的x无关,所以我们可以找出最大的a[i](s[i]<s[tail])以及最大的
- 然后分情况比较,标记,更新。
- 那么——可以用一个堆维护左边的答案,用一个有序序列来维护右边的答案。
- 一旦更新了tail,就把原tail到新tail之间的所有点从右边的序列里丢掉,然后丢到左边的堆里去。
// luogu-judger-enable-o2 #include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const int orzhjw=100010; int a,b,c,qwq=0,Tail=0,last=0,tot=1; int lyf[orzhjw]={0},hy[orzhjw]={0}; bool u[orzhjw]={0},v[orzhjw]={0}; priority_queue<pair<int,int>,vector<pair<int,int> >,less<pair<int,int> > >mid; struct qaq{ int x; int id; }f[orzhjw]={0}; inline int get(int &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline int put(int x){ printf("%d ",x);return 0; } bool cmp(qaq x,qaq y){ return x.x>y.x; } void deal(int l,int r){ for(int i=l;i<=r;i++){ mid.push(make_pair<int,int>(lyf[i],i)); v[i]=1; } } int main(){ get(a); mid.push(make_pair<int,int>(0,0)); for(int i=1;i<=a;i++){ get(hy[i]); } for(int i=1;i<=a;i++){ get(lyf[i]); f[i].id=i;f[i].x=lyf[i]+hy[i]+hy[i]; } sort(f+1,f+a+1,cmp); u[0]=1; for(int k=1;k<=a;k++){ int X,Y,x=mid.top().second,y,ans=0; while(u[x]&&x){mid.pop();x=mid.top().second;} while(v[f[tot].id]&&tot<=a)tot++; y=f[tot].id; X=lyf[x];Y=lyf[y]+hy[y]+hy[y]-last; if(X>Y){ qwq+=X; u[x]=1; mid.pop(); } else{ qwq+=Y; u[y]=1;v[y]=1; tot++; deal(Tail+1,y); Tail=y; } last=hy[Tail]+hy[Tail]; put(qwq);turn; } }
【4/20】(贪心&DP)[USACO08JAN](luogu【P2899】)手机网络Cell Phone Network
树形DP
不会。
贪心
对于一个点以及它的孩子,显然选则根更加划算。
那么我们可以从深度最深的点入手,然后把它的根节点以及兄弟节点都标记掉。
而且在一定情况下,最优的解法一定是尽量少的重复覆盖。
因为说已经最深了,所以它以下的都可以不用理,而且已经标记的点和当前没有关系了,尽情向上就可以了。
然后再重新找一个深度最深的节点,重复这种操作,直到没有节点可以贪心为止。
每一次贪心都取其根节点,ans++;
如图,第一次是红色,第二次是橙色,第三次是黄色。
#include<iostream> #include<cstdio> #include<cstring> #include <cmath> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const int orzhjw=100010; int a,b,c,d,num=0,tot=1,ans=0; bool lyf[orzhjw]={0},u[orzhjw]={0}; int dad[orzhjw]={0}; int ru[orzhjw]={0},chu[orzhjw]={0}; int next[orzhjw*2]={0},to[orzhjw*2]={0},head[orzhjw]={0}; struct qwq{ int id; int dep; }whh[orzhjw]; inline int get(int &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline int put(int x){ printf("%d ",x); } bool cmp(qwq x,qwq y){ return x.dep>y.dep; } void add(int x,int y){ to[++num]=y; next[num]=head[x]; head[x]=num; } int work(int x){ if(x==0)return ans; ans++;u[x]=1; for(int i=head[x];i;i=next[i]){ int y=to[i]; u[y]=1; } while(u[whh[tot].id]){ tot++; } return work(dad[whh[tot].id]); } void build(int x,int dd){ whh[x].dep=dd;whh[x].id=x; lyf[x]=1; for(int i=head[x];i;i=next[i]){ int y=to[i]; if(!lyf[y]){ dad[y]=x; build(y,dd+1); } } return; } int main(){ get(a); for(int i=1;i<a;i++){ int x,y; get(x);get(y); add(x,y);add(y,x); ru[y]++;chu[x]++; if(i==a-1)chu[y]++; if(i==1)ru[x]++; } int op=0; for(int i=1;i<=a;i++){ if(ru[i]==1&&chu[i]==1)op++; } if(op==a){ put((a+2)/3);turn;return 0; } dad[1]=0; build(1,1); sort(whh+1,whh+a+1,cmp); int anss=work(dad[whh[1].id]); if(!u[1])anss++; put(anss);turn; }
【5/20】(数论)luogu 【P1069】细胞分裂
这题我不知道暴力怎么写233333
题意:
已知si,m1,m2
求满足sik % m1m2 ==0的最小k
没有满足的条件就输出 -1
题解:
我们表示si=a1*a2*...*an,那么sik=(p1*a1*p2*a2*...pn*an)k=(p1*a1)k*(p2*a2)k*...*(pn*an)k=k*p1个a1 * k*p2个a2 *...* k*pn个an
m1同理,m1=m2*p1个a1 * m2*p2个a2 *...* m2*pn个an
此处ai为si或m1的一个质因数,pi表示质因数ai的个数
显然,我们可以把分子和分母中相同的ai约掉,直到分母为1为止。
那显然,如果分母有ai,而分子没这个ai,就是无解。
否则k=max(ai分母中的个数 / ai分子中的个数)【记得向上取整】
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const int orzhjw=100010; int a,b,c,d,sum=0,MIN=1000000000; int m1,m2; int whh[orzhjw]={0}; int NEW[orzhjw]={0}; int nxt[orzhjw]={0}; bool lyf[orzhjw]={0}; inline int get(int &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline int put(int x){ printf("%d ",x);return 0; } void find(int pp){ int x=pp; for(int i=2;i;i=nxt[i]){ int o=0; while(x&&x%i==0){ x/=i; o++; } whh[++sum]=o*m2; if(x==1)return; } } int work(int X){ int x=X,ans=0,summ=0; for(int i=2;i;i=nxt[i]){ int o=0; while(x&&x%i==0){ x/=i; o++; } NEW[++summ]=o; if((!o)&&whh[summ])return 0; if(x==1||summ>=sum)break; } if(summ<sum)return 0; for(int i=1;i<=summ;i++) if(NEW[i])ans=max(ans,(whh[i]-1)/NEW[i]+1); if(ans)MIN=min(MIN,ans); return ans; } int main(){ get(a);get(m1);get(m2); if(a==0){ put(-1);turn; return 0; } if(m1==1){ put(0);turn; return 0; } lyf[1]=1; for(int i=2;i*i<=m1;i++) if(!lyf[i]) for(int j=i+i;j<=m1;j+=i) lyf[j]=1; int last=2; for(int i=3;i<=m1;i++){ if(!lyf[i]){ nxt[last]=i; last=i; } } find(m1); for(int i=1;i<=a;i++){ int x;get(x); work(x); } if(MIN==1000000000)put(-1); else put(MIN); turn; }
【6/20】(贪心)[USACO07JAN](luogu【P2878】)保护花朵Protecting the Flowers
首先我们可以得到ans=min(∑Di*sumti);
显然TLE(23333333)
我们假装现在的时间累计是A,有x,y两只牛。
那么如果要让先选x更优,那么我们可以得到一个不等式:
A*Dx + (A+2*Tx) * Dy < A*Dy + (A+2*Ty) * Dy
我们展开,就是A*Dx + A*Dy + 2*Tx*Dy < A*Dy + A*Dx + 2*Ty*Dx;
把两边的A*Dx,A*Dy去掉,再把2去掉,就可以得到
Tx*Dy < Ty*Dx;
没错,我们只要求出满足这样的序列就可以了。
(快排一波~)qwq
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const int orzhjw=100010; ll a,b,c,d,sum=0,ans=0; struct qwq{ ll T; ll D; }whh[orzhjw]={0}; inline ll get(ll &x){ char s=getchar();ll f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%lld ",x);return 0; } bool cmp(qwq x,qwq y){ return x.T*y.D<y.T*x.D; } int main(){ get(a); for(ll i=1;i<=a;i++){ get(whh[i].T);get(whh[i].D); } sort(whh+1,whh+a+1,cmp); for(ll i=1;i<=a;i++){ ans+=sum*whh[i].D; sum+=whh[i].T+whh[i].T; } put(ans); }
【7/20】(数论&找规律&暴力)luogu【P1134】阶乘问题
直接乘:
如果一直暴力乘上去,那么你就完了。因为它的位数上天(OAO);
然后你会发现,每次乘上去都是往前面的位数累加。
而我们只需要最后几位的乘积,前面一坨是没有用的;
因为即使最高位和最高位相乘,那也是会飞上天的位数,但是n的范围并没有那么大。
而后7位乘以某个n得到的答案是和后7位之前的位数无关的。
那就可以取膜掉。
而位数最多有7位,所以我们只需要mod 1e7就行了。
数学解:
我们考虑分解因数:
(此处*1省略掉)
我们可以发现,这些数里有很多偶数,而这些都是2的倍数。
当这些2的倍数与5相乘时,末尾就会至少出现一个0;
而本身末尾就有0的数字我们可以先把0筛掉;
接下来,把2的倍数都筛成非2的倍数,并记录这些数的因数为2的次数和,5也一样。
显然,2的次数比5的次数要多,我们假设它们的差值为k。
筛过之后继续相乘就不会出现10了,那么就可以大胆%10,此时只要顺带乘一遍2k就可以了。
//只有直接乘的代码qwq
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const int orzhjw=0; ll a,b,c,d,ans=1,m=1e7; inline ll get(ll &x){ char s=getchar();ll f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%lld ",x);return 0; } int main(){ get(a); for(int i=1;i<=a;i++){ ans*=i; while(ans&&ans%10==0)ans/=10; ans%=m; } put(ans%10);turn; }
【8/20】(数论)luogu【P3197 】[HNOI2008]越狱
首先看到题目我就懵逼了。。。。这毒瘤数据范围要怎么写啊啊啊啊!!!!!!
冷静!冷静。。。。。
咦,好像可以DP一波然后优化。。。呀,好像过(shi)了(zai)样(gua)例(can)
(兴奋地打了个矩阵快速幂。。。结果才发现只能过m=2的点qwq)
(要不是我不会减法的取膜)(才不会告诉你我去看了题解233)
那我们何妨换成全部情况减去不会越狱的情况?
我们先假设DP[i]表示至今结尾为i的不越狱方案数,那么DP[i]=ΣDP[j](1<=j<=m且j ! = i)
初始都是1,那么有这么个东西:
对!DP[i]=(m-1)*(m-1)*(m-1)*...*(m-1)=(m-1)(i-1)
那么DP[n]就是(m-1)(n-1);
显然,全部情况的方案数就是m*m*m*m...*m=mn;
然后快速幂一下,输出((k(m,m)-(k(m-1,n-1))%m+m)%m;
此处(A-B)%m=(A%m-B%m+m)%m。。。。
(别问我为什么我也不知道。。。。)【下面评论有讲】
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); using namespace std; const ll orzhjw=50010; ll a,b,c,d,m=100003; ll lyf[orzhjw]={0}; inline ll get(ll &x){ char s=getchar();ll f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%lld ",x);return 0; } ll ksm(ll x,ll q){ ll o=1; while(q){ if(q&1)o=(o*x)%m; x=(x*x)%m; q>>=1; } return o; } int main(){ get(a);get(b); put((ksm(a,b)-(a*ksm(a-1,b-1))%m+m)%m); turn; }
【9/20】(贪心)luogu【P1016】旅行家的预算
扯淡:
我们来看一下题目。。。。。。(其实看到这题的时候我对实数是拒绝的。。。)(吐槽:好乱而且其实不是实数的话更难233)
分析一下,对于从一个点x到下一个点y,我们会发现如果:
1.如果Px>Py,那么显然只要把油加到可以跑到Dy就可以了,再在y确定加多少油。
因为如果跑到Dy后面肯定要付出更大的价钱,还不如跑一段Px的再跑一段Py的。
2.如果是在x可以到达的范围内,只有Px<Py:
那么显然,能加就加,现在x把油加满,后面就不会亏了。
总结一下:
对于可走范围内:
1.有比当前小的P,就转移到那个点。
2.没有比当前小的P,把油加满,转移到范围内最小的那个P。
否则:
输出NO Solution
注意:
P[0]=P;
D[N+1]=D1,P[N+1]=0;(对于可走范围:因为情况1的代价是走到下一个需要的,情况2是后面未知的,所以我们不妨让终点从情况1转移过去)
【lyf==D,whh==P】
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); #define emp printf(" "); using namespace std; const int orzhjw=510; int a,b,c,d; int N; float C,D1,D2,P; float lyf[orzhjw]={0},whh[orzhjw]={0}; inline int get(int &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline int put(int x){ printf("%d ",x);return 0; } void output(float x){ printf("%.2f\n",x);exit(0); } void work(int now,float sum,float ans,float last){ if(sum>=D1)output(ans); int MIN=now+1; for(int i=now+1;i<=N;i++){ if(lyf[now]+D2*C<lyf[i])break; float X=lyf[i]-lyf[now]; if(whh[i]<whh[now]){ if(last<=X)work(i,lyf[i],ans+(X-last)/D2*whh[now],0); else work(i,lyf[i],ans,last-X); } } for(int i=now+1;i<=N;i++){ if(lyf[now]+D2*C<lyf[i])break; if(whh[MIN]>whh[i])MIN=i; } float X=(C*D2-last)/D2; if(lyf[MIN]<=lyf[now]+D2*C)work(MIN,lyf[MIN],ans+X*whh[now],C*D2-lyf[MIN]+lyf[now]); printf("No Solution\n"); exit(0); } int main(){ scanf("%f%f%f%f%d",&D1,&C,&D2,&P,&N); for(int i=1;i<=N;i++){ scanf("%f%f",&lyf[i],&whh[i]); } N++; whh[0]=P; lyf[N]=D1;whh[N]=0; work(0,0,0,0); }
【10/20】(贪心)luogu【P1525】关押罪犯
分析:
题目给你一个图,让你把这个图分成两个集合,使得两个集合里面最大边权最小。
显然,这个答案就在现有的边权里面,而且越多大的消失越好。
因为说是找最大的,那么显然当前只要考虑最大的那条边。
只有把最大的那条边去掉,即分开此边的两端点,才会改变答案的大小。
解决:敌人的敌人是我朋友
我们不妨将最终结果看成“没有e1且没有e2且没有e3且.....且没有eq”(这里1-q是从大到小排序的)
也就是说,我们尽早把大的边的两端吸掉仇恨,那答案就会变小;反之,如果吸掉别的边,当前答案是不会改变的。
所以,当前最大边,都要建立起所连接点的对立关系。
我们用一个数组agst[i]来表示:与 i 对立的集合。
如果两个都暂时是单独的一个点(agst[x]==agst[y]==0)(CE):那就agst[]相互赋值
如果x是单独一个点(agst[x]==0):那就建立起gast[x]=y
如果y是单独一个点(agst[y]==0):那就建立起gast[y]=x
如果两个都有且不再同一集合(gast[x]&&agst[y]&&x!=y):那就合并x和agst[y],y和agst[x];
如果x和y在同一集合里,也就是说在满足比当前边大的左右边消失的情况下,在某个阵营里最大:输出答案,return 0;
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); #define emp printf(" "); using namespace std; const int orzhjw=100010; int a,b,c,d,sum=0; int agst[orzhjw]={0},dad[orzhjw]={0}; struct qwq{ int from; int to; int w; }edge[orzhjw]={0}; inline int get(int &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline int put(int x){ printf("%d ",x);return 0; } bool cmp(qwq x,qwq y){ return x.w>y.w; } int find(int x){ if(dad[x]==x)return x; return dad[x]=find(dad[x]); } int main(){ get(a);get(b); for(int i=1;i<=a;i++)dad[i]=i; for(int i=1;i<=b;i++){ int x,y,z; get(x);get(y);get(z); edge[i].from=x;edge[i].to=y; edge[i].w=z; } sort(edge+1,edge+b+1,cmp); for(int i=1;i<=b;i++){ int x=edge[i].from,y=edge[i].to,z=edge[i].w; int X=find(dad[x]),Y=find(dad[y]); if(X==Y){ put(z);turn;return 0; } if(agst[X]==0&&agst[Y]==0){ agst[X]=Y;agst[Y]=X; } if(agst[X]){ dad[Y]=find(dad[agst[X]]); } if(agst[Y]){ dad[X]=find(dad[agst[Y]]); } } put(0);turn; return 0; }
【11/20】(数论)luogu【p1414】又是毕业季II
分析:
题目要你从n个数里选i个数出来(1<=i<=n),问你取出i个时可以得到的最大公约数是多少?
tip1:显然取得越少答案越大;
tip2:肯定是有共同的一个因数;
解决:
我们可以将每一个数拆成若干个因数,每个因数k都在d[k]加上一个权值。
那么我们只要从小到大排序一波d[i],然后ans[d[i]]=k对应的因数。
这时候我们取个数为d[i]的对应k的最大值就好了。
显然,当选取一个因数为k时,d[i]统计的不会重复,恰好就是有这个因数的数字的个数。
显然如果没有恰好是当前取数i个的d[j],记得向上取max。
(对于一个取值数i,如果没有刚好的d[],答案就是大取值里可以得到的最大值。
毕竟ans[i]只能在取数大于i的ans[k]里面找,因取值小的满足不了个数>=i个的条件)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); #define emp printf(" "); using namespace std; const int orzhjw=1000010; int a,b,c,d,mx=0; int ans[orzhjw]={0}; struct qwq{ int id; int sum; }whh[orzhjw]={0}; inline int get(int &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline int put(int x){ printf("%d ",x);return 0; } bool cmp(qwq x,qwq y){ if(x.sum<y.sum)return 1; if(x.sum>y.sum)return 0; return x.id<y.id; } void add(int x){ for(int i=1;i*i<=x;i++){ if(x%i==0){ int j=x/i; whh[i].sum++;whh[j].sum++; if(i==j)whh[i].sum--; } } } int main(){ get(a); for(int i=1;i<=a;i++){ int x; get(x);add(x); mx=max(mx,x); } for(int i=1;i<=mx;i++)whh[i].id=i; sort(whh+1,whh+mx+1,cmp); for(int i=1;i<=mx;i++){ //put(whh[i].id);put(whh[i].sum);turn; ans[whh[i].sum]=whh[i].id; } ans[a+1]=1; for(int i=a;i>=1;i--)ans[i]=max(ans[i],ans[i+1]); for(int i=1;i<=a;i++){ put(ans[i]);turn; } return 0; }
【12/20】(贪心)luogu【P1645】 序列
分析:
经典贪心。
题目要求最大长度,即最大的重合。(贪心思想)
我们可以从每个区间的选择先后关系来看。
解决:
对于当前区间,选择的数肯定是要最大程度地重合。(即对答案的贡献最大)
那么我们可以看作“为后面服务”——按照右端点排序。
遇到一个区间时,如果它所在的区域已经被前面的占用,且占用的数量大于等于要求的,就continue。
每次选择的数肯定是贪心地从最右边开始挑。
如果一个点已经被访问过了,那么你很幸运,跳过。没被访问过得点就标记顺便累加答案。
这样,就是每一个区间的选择都对后面的贡献最大。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); #define emp printf(" "); using namespace std; const int orzhjw=1010; int a,b,c,d,mx=0,ans=0; bool lyf[orzhjw]={0}; struct qwq{ int l; int r; int kk; }ask[orzhjw]={0}; inline int get(int &x){ char s=getchar();int f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline int put(int x){ printf("%d ",x);return 0; } bool cmp(qwq x,qwq y){ return x.r<y.r; } int main(){ get(a); for(int i=1;i<=a;i++){ int x,y,z; get(x);get(y);get(z); ask[i].l=x;ask[i].r=y;ask[i].kk=z; } sort(ask+1,ask+a+1,cmp); ask[0].r=1; for(int i=1;i<=a;i++){ int x=ask[i].l,y=ask[i].r,k=ask[i].kk; int tot=0; for(int j=x;j<=y;j++)if(lyf[j])tot++; k-=tot; if(k<=0)continue; for(int j=y;j>=x&&k;j--)if(!lyf[j]){k--;ans++;lyf[j]=1;} } put(ans);turn; }
【13/20】(数论)luogu【P4296】[AHOI2007]密码箱
分析:
题目要求:x*x=kn+1 x*x-1=kn (x+1)*(x-1)=kn
解决:
显然,我们可以分解 k=k1*k2,n=n1*n2 所以一定有一种分解,
使得:
1.(x+1)=k1*n1且(x-1)=k2*n2;
2.(x+1)=k2*n2且(x-1)=k1*n1;
枚举n的每个因子n1,n2=n/n1。若n1<=n2,那么再枚举k2:
1.(x-1)=k2*n2(0<=k1<=n1),那么x就是k2*n2+1,(x+1)=k2*n2+2=k1*n1 【只要判断整数k1是否存在就好了】
2.(x+1)=k2*n2(1<=k<=n1),那么x就是k2*n2-1,(x-1)=k2*n2-2=k1*n1【一样,判断k1】
记得用map去重。
效率是所以因数之和。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<map> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); #define emp printf(" "); using namespace std; const int orzhjw=2000010; ll a,b,c,d,tot=0; map<ll,bool>lyf; ll ans[orzhjw]={0}; inline ll get(ll &x){ char s=getchar();ll f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%lld ",x);return 0; } int main(){ get(a); for(ll i=1;i*i<=a;i++){ ll x=i,y=a/i; if(x*y==a){ for(ll j=1;j<=a;j+=y) if((j+1)%x==0&&(!lyf[j])){ans[++tot]=j;lyf[j]=1;} for(ll j=y-1;j<=a;j+=y) if((j-1)%x==0&&(!lyf[j])){ans[++tot]=j;lyf[j]=1;} } } sort(ans+1,ans+tot+1); for(ll i=1;i<=tot;i++){ put(ans[i]);turn; } return 0; }
【14/20】(数论)luogu【P1463】 [POI2002][HAOI2007]反素数
分析:
令x=p1k1*p2k2*...*pmkm (p为质因数)(显然有这样的唯一分解)
假设pi<pj
若存在任意的ki<kj
则可以通过交换ki和kj,得到x'<=x
并且g(x)=g(x')
那么显然,x'<x,而g(x)==g(x'),x不是反素数。
即k递减。
解决:
显然,答案就是约数最大的数字的最小值。而约数计算可以通过质因数来递推起来。
设当前因数为pk (p为一个质因数),那么cnt=cnt+cnt*i。
可以看出,只与质因数的种类和指数有关,即让质因数种类尽量多。
那么你会发现无论如何,质数可以尽量靠前就靠前就靠前,即到第12个质数。
然后根据前12个质数,以及质因数指数递减,爆搜就好了。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); #define emp printf(" "); using namespace std; const ll orzhjw=20; ll a,b,c,d,ans=1,mx=1; ll whh[13]={0,2,3,5,7,11,13,17,19,23,29,31,37}; inline ll get(ll &x){ char s=getchar();ll f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%lld ",x);return 0; } ll find(ll now,ll last,ll cnt,ll k){//当前乘积,上一个的指数,总个数,第k个质数 if(cnt>mx){ mx=cnt;ans=now; } if(cnt==mx){ ans=min(ans,now); } if(k>12||last==0)return ans; ll qwq=whh[k]; find(now,last,cnt,k+1); for(ll i=1;i<=last;i++){ if(now*qwq>a)break; find(now*qwq,i,cnt+cnt*i,k+1); qwq*=whh[k]; } return ans; } int main(){ get(a); find(1,31,1,1); put(ans); turn; return 0; }
【15/20】(数论)luogu【5174】圆点
分析:
嗯好我又回来了
不要慌,虽然是个圆,但还是挺简单的。
圆内?r!勾股定理!
只要把所有X2+Y2(<=R)累加起来就好了。
解决:
我们知道,圆是轴对称图形,又是中心对称图形。
所以只要求出一个象限的解,就可以求出整个圆的解。
假装我们现在只有第一象限。
一:对于一个x,我们可以发现答案的累加形式就是
ans+=x2+12+x2+22...+x2+y2
=x2*y+sumpower(1,y)
我们知道,1到y的平方和等于
那么只要用sqrt的向下取整,就可以算出来每一个x的贡献。
二:从累加条件的角度思考,我们发现当横坐标为x或纵坐标为x时,x才有贡献。
也就是说,只要求出在这两条直线上找出符合条件的点,其个数就是x2累加的次数。
即还是枚举x,求出对应的y,然后ans+=2*y*x2
作法二的代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #define ll long long #define ull unsigned long long #define turn printf("\n"); #define emp printf(" "); using namespace std; const int orzhjw=50010; ll R,b,c,d,m=1e9+7,ans=0,tot=0; inline ll get(ll &x){ char s=getchar();ll f=1,o=0; while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){o=o*10+s-'0';s=getchar();} return x=o*f; } inline ll put(ll x){ printf("%lld ",x);return 0; } int main(){ get(R); for(ll x=1;x*x<R;x++){ ll k=R-x*x; ll y=sqrt(k); ans+=(((2*y*x)%m)*x)%m; ans%=m; } for(ll i=1;i*i<=R;i++){ (ans+=i*i)%=m; } put((ans*4)%m);turn; }