ε=(´ο`*))) 这些笔记集合什么的要是其他大佬来csdn写绝对写的远超于我qwq,分析也更加深刻,蒟蒻在此瑟瑟发抖,临近考试将自己会的不会的旧的新学的知识稍微罗列出来。因为是蒟蒻嘛,文章择优而取,有错误请指出,不胜感激qwq
内容稍长稍多,也是择其喜好而取吧w
那么大致来个索引先:
文章目录
=基础算法=
滚动数组
新学的姿势,详情参见
http://blog.csdn.net/qq_36693514/article/details/78279869
滚动数组笔记
char ch[100010];
int f[5][4][4][4][4],ans,n;//第一维滚动起来
int main(){
memset(f,-1,sizeof(f));
f[0][0][0][0][0]=0;
read(n);
scanf("%s",ch);
for(ri i=0;i<n;i++)//这里的枚举次数不变
for(ri a1=0;a1<=3;a1++)
for(ri a2=0;a2<=3;a2++)
for(ri b1=0;b1<=3;b1++)
for(ri b2=0;b2<=3;b2++)
{
ri x=i%4,y=(i+1)%4;//在这里%下
...
}
for(ri a1=0;a1<=3;a1++)
for(ri a2=0;a2<=3;a2++)
for(ri b1=0;b1<=3;b1++)
for(ri b2=0;b2<=3;b2++)
ans=max(ans,f[n%4][a1][a2][b1][b2]);//最后输出时
printf("%d",ans);
return 0;
}
尺取法
题目:http://blog.csdn.net/consciousman/article/details/52348439
题解 http://blog.csdn.net/qq_36693514/article/details/78320753
LL sum,s,qu[100010];
int i,j,n,t,ans=inf;
int main(){
cin>>t;while(t--)
{
cin>>n>>s;
for(i=0;i<n;++i) cin>>qu[i];
int l=0,r=0;
ans=inf,sum=0;
while(1)
{
while (r<n && sum<s) sum += qu[r++];
if (sum < s) break;
ans = min(ans,r-l);
sum -= qu[l++];
}
if(ans==inf) ans=0;
cout<<ans<<'\n';
}
}
分治
double a,b,c,d,x,x1,x2;
double f(double x){return (x*x*x*a+b*x*x+c*x+d);}
int main(){
cin>>a>>b>>c>>d;
for (x=-10000;x<=10000;x++){
x1=(x-0.05)/100;x2=(x+0.05)/100;
if (f(x1)*f(x2)<0||f(x1)==0)
printf("%.2f ",x/100);
}
}
堆排序
这里是小根堆排序,更改下重载即可实现大根堆
struct ed{
int num;
};
bool operator < (ed a,ed b){return a.num > b.num;}
priority_queue<ed>q;
int main(){
ed x;int n;rd(n);
for(ri i=1;i<=n;++i){
rd(x.num);
q.push(x);
}
while(!q.empty()){
printf("%d ",q.top());
q.pop();
}
}
双向宽搜
eg:迷宫
根据实验测得下列结论:
耗时最长:调用队列
耗时次之:手写队列
耗时最短:双向宽搜
双向宽搜可是个好东西,能够将搜索的时间复杂度指数级的降低,做到218–>29
struct ed{
int x,y;
}qa[sz*sz],qb[sz*sz],qc[sz*sz];
typedef long long LL;
#define ri register int
ed *q1=qa,*q2=qb,*q3=qc;
char mmp[sz][sz];
int t,n,f1,f2,f3,t1,t2,t3;int X[]={1,-1,0,0},Y[]={0,0,1,-1};
bool vis1[sz][sz],vis2[sz][sz],f;
inline void init(){
memset(vis1,0,sizeof vis1);memset(vis2,0,sizeof vis2);
q1=qa,q2=qb,q3=qc;f1=f2=f3=t1=t2=t3=0;f=0;}
#define can(x,y) (x>0&&y>0&&x<=n&&y<=n)
inline void exbfs(){
while(f1<t1&&f2<t2){
if(t1-f1<t2-f2){
t3=0;
while(f1<t1){
ed u=q1[f1++];
for(ri i=0;i<4;++i){
int x=u.x+X[i],y=u.y+Y[i];
if(can(x,y)){
if(vis2[x][y]){//正向找到终点or反向遍历到的点
f=1;return; }
if(!vis1[x][y]&&mmp[x][y]!='#')
vis1[x][y]=1,q3[t3++]=(ed){x,y};
}
}
}
swap(q1,q3);f1=0,t1=t3;
}
else
{
t3=0;
while(f2<t2){
ed u=q2[f2++];
for(ri i=0;i<4;++i){
int x=u.x+X[i],y=u.y+Y[i];
if(can(x,y)){
if(vis1[x][y]){
f=1;return ;}
if(!vis2[x][y]&&mmp[x][y]!='#')
vis2[x][y]=1,q3[t3++]=(ed){x,y};
}
}
}
swap(q2,q3);f2=0,t2=t3;
}
}
}
int main(){
scanf("%d",&t);while(t--)
{
scanf("%d",&n);gets(mmp[0]);
for(ri i=1;i<=n;++i) gets(mmp[i]+1);
init();
for(ri i=1;i<=n;++i)for(ri j=1;j<=n;++j)
if(mmp[i][j]=='s')
vis1[i][j]=1,q1[t1++]=(ed){i,j};
else if(mmp[i][j]=='e')
vis2[i][j]=1,q2[t2++]=(ed){i,j};
exbfs();
if(f) puts("YES");
else puts("NO");
}
高精家族
高精N则运算
struct qwq{
int a[100000];
int len;
};
qwq operator + (qwq x,qwq y){
int le=max(x.len,y.len);
for(RI i=1;i<=le;++i)
{
x.a[i]+=y.a[i];
x.a[i+1]+=x.a[i]/10;
x.a[i]%=10;
}
return x;
}
qwq operator - (qwq x,qwq y){
int le=max(x.len,y.len);
for(RI i=1;i<=le;++i)
{
x.a[i]-=y.a[i];
if(x.a[i]<0)
{
x.a[i+1]-=1;
x.a[i]+=10;
}
}
return x;
}
qwq operator * (qwq x,qwq y){
qwq c;
for(RI i=1;i<=5000;++i)
c.a[i]=0;
for(RI i=1;i<=x.len;++i)
for(RI j=1;j<=y.len;++j)
c.a[i+j-1]+=x.a[i]*y.a[j];
for(RI i=1;i<=5000;++i)
{
c.a[i+1]+=c.a[i]/10;
c.a[i]%=10;
}
return c;
}
qwq A,B,C;
void out(){
int h=0;
for(RI i=4950;i>=1;i--){
int q=C.a[i];
if(q!=0 && h==0) h=1;
if(h==1) printf("%d",q);
if(h==0&&i==1) printf("%d",0);
}
}
char s[1000000];
int main()
{
scanf("%s",s);int lenx=strlen(s);
for(RI i=0;i<lenx;++i) A.a[lenx-i]=s[i]-'0';
A.len=lenx;
scanf("%s",s);lenx=strlen(s);
for(RI i=0;i<lenx;++i) B.a[lenx-i]=s[i]-'0';
B.len=lenx;
C=A*B;//C=A+B;C=A-B;
out();
}
高精阶乘
int m=0;
read(n);a[1]=1;
for(RI i=1;i<=n;++i)
{
for(RI j=1;j<=la;++j)
{
a[j]*=i;
a[j]+=m;
m=a[j]/10;
a[j]=a[j]%10;
}
while(m>0)
{
la++;
a[la]+=m%10;
m/=10;
}
}
for(RI i=la;i>=1;i--) write(a[i]);
高精阶乘の和
int a[100000],n,i,y,xy[100000],s[100000];//s[0]和a[0]表示两个数组的长度
//s表示答案,a表示阶乘,先算出阶乘,放在a里,再把s和它相加,更新s
void add(){//表示s=s+a
int i;
memset(xy,0,sizeof(xy));//xy为辅助数组,先将a+s放入xy,再将s更新为xy
xy[0]=max(s[0],a[0]);//更长的为xy数组长度
for (i=1;i<=xy[0];i++)
{
xy[i]+=s[i]+a[i];//将每一位相加
xy[i+1]=xy[i]/10;//进位
xy[i]%=10;//进位
}
while (xy[xy[0]+1]>0) //进位
{
xy[xy[0]+2]=xy[xy[0]+1]/10;
xy[xy[0]+1]%=10;
xy[0]++;
}
s[0]=xy[0];//长度也要更新
for (i=1;i<=xy[0];i++)s[i]=xy[i];//将xy给s
}
int main(){
cin>>n;
a[0]=1; a[1]=1;//一定要是1,不然算阶乘为0
s[0]=1; s[1]=0;
for (y=1;y<=n;y++){ //高精度乘法
memset(xy,0,sizeof(xy));
xy[0]=a[0];
for (i=1;i<=a[0];i++)
{
xy[i]+=a[i]*y;//阶乘
xy[i+1]=xy[i]/10;//进位
xy[i]%=10;
}
while (xy[xy[0]+1]>0)//进位
{
xy[xy[0]+2]=xy[xy[0]+1]/10;
xy[xy[0]+1]%=10;
xy[0]++;
}
for (i=1;i<=xy[0];i++) a[i]=xy[i];//算出y!放入a内
a[0]=xy[0];
add();
}
for (i=s[0];i>=1;i--) cout<<s[i];
阶乘分解
void find_prime(int n){
for(RI i = 2;i <= n;i ++){
if(!he[i]) prime[++ tot] = i;
for(RI j = 1;j <= tot && prime[j] * i <= n;j ++){
he[prime[j] * i] = 1;
if(i % prime[j] == 0) break;
}
}
}
void ask_jc(int n){
p = 0;int t = n;
for(RI i = 1;i <= tot && prime[i] <= n;i ++){
int c = prime[i];
while(c <= n){
cnt[i] += t / c;
c *= c;
p = i;
}
}
}
int main(){
memset(he,0,sizeof(he));memset(prime,0,sizeof(prime));
scanf("%d",&n);
find_prime(n);
ask_jc(n);
for(RI i = 1;i <= p;i ++)
if(cnt[i]){
for(RI j = 1;j <= cnt[i];j ++)
printf("%d ",prime[i]);
}
归并排序+求逆序对
归并排序
int n,num[sz],tmp[sz];
void merge_sort(int l,int r){
if(l==r) return;
int mid=l+r>>1;
merge_sort(l,mid),merge_sort(mid+1,r);
int p=l,pl=l,pr=mid+1;
while(pl<=mid||pr<=r)
{
if(pr>r|| (pl<=mid&&num[pl]<=num[pr]) )
tmp[p++]=num[pl++];
else
tmp[p++]=num[pr++];
}
for(int i=l;i<=r;++i)num[i]=tmp[i];
}
int main(){
cin>>n;
for(int i=1;i<=n;++i) cin>>num[i];
merge_sort(1,n);
for(int i=1;i<=n;++i) printf("%d ",num[i]);
}
是求逆序对的好方式
ll a[sz],tmp[sz];ll ans,n;
inline void merge(ll l,ll m,ll r)
{
ll i=l; ll j=m+1; ll k=l;
while(i<=m&&j<=r){
if(a[i]>a[j]){
tmp[k++]=a[j++];
ans+=m-i+1;
}
else
tmp[k++]=a[i++];
}
while(i <= m) tmp[k++]=a[i++];
while(j <= r) tmp[k++]=a[j++];
for(int i=l;i<=r;i++)
a[i] = tmp[i];
}
inline void merge_sort(ll l,ll r){
if(l<r){
ll m=(l+r)>> 1;
merge_sort(l,m);
merge_sort(m+1,r);
merge(l,m,r);
}
}
int main(){
read(n);for(ll i=0;i<n;i++) read(a[i]);
merge_sort(0,n-1);
cout<<ans;
}
手读手输(快读)
各种版本
这个随时随地用得上,对于100w以上输入的数据效果显著,多用于大数据输入输出
①读数字型
inline void read(int &x){
x = 0;bool flag = 0;char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') flag = 1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
if(flag) x *= -1;
}
inline void write(int x){
if(x < 0) putchar('-'),x *= -1;
if(x / 10) write(x / 10);
putchar(x % 10 + '0');
}
②读串型
int read(string a){
int x=0;
if(a[0]=='-'){
for(int i=1;i<a.length();i++)
if(a[i]>='0'&&a[i]<='9')
x = x*10+a[i]-'0';
x=-x;
}
else{
for(int i=0;i<a.length();i++)
if(a[i]>='0'&&a[i]<='9')
x = x*10+a[i]-'0';
}return x;
}
更多优化卡时等等详情请见qwq不断更新的总结
=动态规划=
类型太多简单列列吧qwq,不太想乖乖整理了,把最近做的几个拿出来吧ε=(´ο`*))) 还有高精dp真是。。。↑_↓背包
http://blog.csdn.net/lyhvoyage/article/details/8545852
(个人认为这个不知名大佬写的不错,再就是去看看背包九讲的前几讲emmmmm)
01
有N件物品和一个容量为V的背包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大
eg:http://codevs.cn/problem/5709/
特征:明确的选择关系,给出体积限定,或物品数量为1
int a[sz],v,n,ans;
int w[sz],k[sz],dp[1010][1010];
int main()
{
rd(v),rd(n);for(ri i=1;i<=n;++i)rd(w[i]),rd(k[i]);
for(ri i=1;i<=n;++i)
for(ri j=1;j<=v;j++)
if(j>=w[i])
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+k[i]);
else
dp[i][j]=dp[i-1][j];
cout<<dp[n][v];
}
这是二维转移,对于第一维我们可以优化掉,优化空间
int F[1001],c[1001],v[1001];
int main(){
int s,n;cin>>s>>n;
for(int k=1;k<=n;k++) cin>>c[k]>>v[k];
for(int i=1;i<=n;i++)
for(int j=s;j>=c[i];j--)//那个物体可以放进去
F[j]=max(F[j],F[j-c[i]]+v[i]);
cout<<F[s];//询问能否将一个容量s的背包填到最大价值
}
完全
特征:物品数量无限,追求总和最大
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大
同样有二维和一维写法
/*完全背包 每一个物品都有无限个
状态转移方程,d[i][j]=max(dp[i-1][j],dp[i-1][j-k*c[i]]+k*w[i]),if(j-k*c[i]>=0)
边界 dp[0][j]=0 j>=0&&j<=v尽量填充背包,不要求装满 dp[0][0]=0 dp[0][j]=-INF j>=1&&j<=v 恰好装满*/
const int V=1000,N=100;int dp[N][V],c[N],w[N],v,n;
int main()
{
memset(dp,0,sizeof(dp));
cin>>n>>v;
for(int i=1;i<=n;i++) cin>>c[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=v;j++)
dp[i][j]=dp[i-1][j];
for(int j=c[i];j<=v;j++)
for(int k=1;k*c[i]<=j;k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*c[i]]+k*w[i]);
}
cout<<dp[n][v]<<" ";
}
一维写法:
int f[N],c[N],w[N],v,n;
int main(){
cin>>v>>n;
for(int i=1;i<=n;i++)
cin>>c[i]>>w[i];
for(int i=1;i<=n;++i)
for(int j=c[i];j<=v;++j)
f[j]=max(f[j],f[j-c[i]]+w[i]);
cout<<f[v];
}
多重
特征:是每件物品给出确定的件数,求可得到的最大价值
多重背包问题要求简单,就是每件物品给出确定的件数,求可得到的最大价值
多重背包转换成 01 背包问题就是多了个初始化,把它的件数C 用二进制分解成若干个件数的集合,这里面数字可以组合成任意小于等于C的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可以用数字的二进制形式来解释
比如:7的二进制 7 = 111 它可以分解成 001 010 100 这三个数可以组合成任意小于等于7 的数,而且每种组合都会得到不同的数拆分出来不全为1的数,比如13 = 1101 则分解为 0001 0010 0100 0110,前三个数字可以组合成 7以内任意一个数,即1、2、4可以组合为1——7内所有的数,加上 0110 = 6 可以组合成任意一个大于6 小于等于13的数,比如12,可以让前面贡献6且后面也贡献6就行了。虽然有重复但总是能把 13 以内所有的数都考虑到了,基于这种思想去把多件物品转换为,多种一件物品,就可用01 背包求解
二进制分解+01
int T,V,n,i,j,k,v[sz],w[sz],c[sz],dp[sz];
//v[]存价值,w[]存尺寸,c[]存件数 本题中价值是米重量,尺寸是米价格
int main()
{
int sum,Value[sz],size[sz];
//sum存储分解完后的物品总数
//Value存储分解完后每件物品的价值
//size存储分解完后每件物品的大小
cin>>T;while(T--)
{
sum=0;
cin>>V>>n;
for(i=0;i<n;i++)
{
cin>>w[i]>>v[i]>>c[i];
//对该种类的c[i]件物品进行二进制分解
for(j=1;j<=c[i];j<<=1){//相当于x2
Value[sum]=j*v[i];
size[++sum]=j*w[i];
c[i]-=j;
}
if(c[i]>0){
Value[sum]=c[i]*v[i];
size[++sum]=c[i]*w[i];
}
}
//经过上面对每一种物品的分解,
//现在Value[]存的就是分解后的物品价值
//size[]存的就是分解后的物品尺寸
//sum就相当于原来的n 下面就直接用01背包算法来解
memset(dp,0,sizeof(dp));
for(i=0;i<sum;i++)
for(j=V;j>=size[i];j--)
if(dp[j]<dp[j-size[i]]+Value[i])
dp[j]=dp[j-size[i]]+Value[i];
cout<<dp[V]<<endl;
}
return 0;
}
两种包一起
int n,m,dp[1010],t;int p[1010],h[1010],c[1010];
void wan(int v,int w){
for(int i=v;i<=n;i++)
if(dp[i]<dp[i-v]+w)
dp[i]=dp[i-v]+w;
}
void ling(int v,int w){
for(ri i=n;i>=v;--i)
if(dp[i]<dp[i-v]+w)
dp[i]=dp[i-v]+w;
}
int main()
{
rd(t);while(t--)
{
memset(dp,0,sizeof(dp));
rd(n),rd(m);
for(ri i=1;i<=m;++i)
{
rd(p[i]),rd(h[i]),rd(c[i]);
if(p[i]*c[i]>=n)
wan(p[i],h[i]);
else
{
for(ri j=1;j<c[i];j<<1){
ling(j*p[i],j*h[i]);
c[i]=c[i]-j;
}
}
}
printf("%d\n",dp[n]);
} return 0;
}
混合
特征:限定不明确,包含三种背包的影子,求最大值
背包体积为V ,给出N个物品,每个物品占用体积为Vi,价值为Wi,每个物品要么至多取1件,要么至多取mi件(mi > 1) , 要么数量无限 , 在所装物品总体积不超过V的前提下所装物品的价值的和的最大值是多少?
有依赖背包
特征:明确的限定捆绑关系,体积or价值限定,求最大值
划分型
目前涉及到的划分型dp不多,算上数的划分这个题目的话也是寥寥几个,不知道总结的对不对,总之先把自己的想法写出来
eg:乘积最大 http://codevs.cn/problem/1017/
划分型dp和区间型dp区分上目前还未细致研究,但是听说挺相似的2333
对于划分dp应该是有明显的划分条件,将什么什么划分到哪里or在一个连续的阶段中插入分层处理。
个人觉得有时候这个分析很不好分析,对于这种问题我们要结合连续阶段分析划分的情况怎么处理,有时候我们会选择三层for循环来枚举断点,在断点处进行选择插入断点新情况和不选择插入的老情况的比较,进行答案的统计和更新
int a[sz][sz],f[sz][sz],n,m;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%c",&x[i]),a[i][i]=x[i]-'0';
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
a[i][j]=a[i][j-1]*10+x[j]-'0';//i位->j位的数字
for(int i=1;i<=n;i++)
f[i][0]=a[1][i];//初始化
for(int k=1;k<=m;k++)//循环分割次数
for(int i=k+1;i<=n;i++)//分割k次至少需要k+1位数字
for(int j=k;j<i;j++)//循环分割位置
f[i][k]=max(f[i][k],f[j][k-1]*a[j+1][i]);//前面*后面的数字
printf("%d\n",f[n][m]);
}
区间型
const int maxn = 110, INF = 100000000;
int n, a[maxn], sum[maxn], dp[maxn][maxn];
void solve()
{
for (int i = 1; i <= n; i++)
for (int j = i - 1; j >= 0; j--)
{
dp[j][i] = INF;
for (int k = j; k < i; k++)
dp[j][i] = min(dp[j][i], dp[j][k] + dp[k+1][i] + sum[i] - sum[j - 1]);
}
cout << dp[1][n] << endl;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++){
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
solve();
}
状压型
http://www.lydsy.com/JudgeOnline/problem.php?id=1087
LL dp[10][100][521],ans;
LL tot,n,K,stay[101],cnt[101],pos;
//stay记录每种状态压缩后的值,cnt记录对应的状态中1的个数
bool mmp[101][101];
void dfs(LL k,LL put,LL pos)
//预处理,k是放了几颗,put是当前放的位置,now是当前状态压缩后的值
{
stay[++tot]=pos;
cnt[tot]=k;
if(k>=(n+1)/2 || k>=K)
return;
for(RI i=put+2;i<=n;++i)
dfs(k+1,i,pos+(1<<(i-1)));
}
void init()
{
dfs(0,-1,0);//预处理出每种状态,共有tot种
for(RI i=1;i<=tot;++i)
for(RI j=1;j<=tot;++j)
mmp[i][j]=mmp[j][i]=( (stay[i]&stay[j]) || ((stay[i]>>1)&stay[j]) || ((stay[i]<<1)&stay[j]))?0:1;
//枚举某两种状态是否能相邻.一共有三种不能的情况:上下都是1,左上、右下是1,左下、右上是1
for(RI i=1;i<=tot;++i)
dp[1][cnt[i]][i]=1ll;//边界
}
int main()
{
freopen("FKing.in","r",stdin);
freopen("FKing.out","w",stdout);
read(n),read(K);
init();
for(RI i=2;i<=n;++i)
for(RI j=0;j<=K;++j)
for(RI pos=1;pos<=tot;++pos)
{
if(cnt[pos]>j)
continue;
for(RI k=1;k<=tot;++k)
if(mmp[k][pos]&&cnt[k]+cnt[pos]<=j)
dp[i][j][pos]+=dp[i-1][j-cnt[pos]][k];//转移
}
for(RI i=1;i<=tot;++i)
ans+=dp[n][K][i];
树型
eg:二维:没有上司的舞会
三维:愚蠢的矿工
树型DP的套路一般是处理树结构上的问题,当然区间和序列也可以转化成树来处理。树型DP出现指数级枚举组合数时,采用左儿子右兄弟法来处理
int fir[sz],nxt[sz],dp[sz][2];int f,t;int root,tot = 1,n,ru[sz];
bool use[sz];
void dfs(int p,int f){
use[p] = 1;
for(RI i = fir[p] ; i ; i = nxt[i])
{
int t = l[i].t;
if(t != f && use[t]!=1){
use[t] = 1;
dfs(t,p);
dp[p][0] = max(dp[p][0],dp[t][1]+dp[p][0]);
dp[p][1] = max(dp[p][1],max(dp[t][1],dp[t][0])+dp[p][1]);
}
}
}
int main(){
cin>>n;for(RI i = 1 ; i <= n ; i ++)scanf("%d",&dp[i][0]);
for(RI i = 1 ; i < n ; i ++)
{
scanf("%d%d",&f,&t);
ru[f] ++;
build(f,t),build(t,f);
}
scanf("%d%d",&root,&root);
for(RI i = 1 ; i <= n ; i ++)
if(ru[i] == 0){
root = i;
break;
}
dfs(root,-1);
cout<<max(dp[root][0],dp[root][1])<<'\n';
}
棋盘型
eg:传纸条
前两个和后两个分别代表了两条不同的路径的状态,通过不同的转移(主要是那一长串max)来完成答案的统计
int m,n,x,f[60][60][60][60],a[55][55];
int main()
{
cin>>m>>n;
for(RI i=1;i<=m;++i)
for(RI j=1;j<=n;++j){
cin>>x; a[i][j]=x;
}
for(RI i=1;i<=m;++i)for(RI j=1;j<=n;++j)for(RI k=1;k<=m;++k)for(RI l=1;l<=n;++l){
f[i][j][k][l]=max( f[i-1][j][k-1][l], max(f[i][j-1][k-1][l],max(f[i-1][j][k][l-1],f[i][j-1][k][l-1]) ) )+a[i][j]+a[k][l];
if(i==k&&j==l)
f[i][j][k][l]-=a[i][j];
}
printf("%d",f[m][n][m][n]);
return 0;
}
还有像是乌龟棋这样的棋盘型的核心
f[0][0][0][0]=step[1];
for(int i=0;i<=s1;i++)for(int j=0;j<=s2;j++)for(int k=0;k<=s3;k++)for(int l=0;l<=s4;l++){
now=i+j*2+k*3+l*4+1;
if(i>0) f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l]+step[now]);
if(j>0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l]+step[now]);
if(k>0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k-1][l]+step[now]);
if(l>0) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k][l-1]+step[now]);
}
printf("%d\n",f[s1][s2][s3][s4]);
递推型
其实感觉这个更像是递推什么的emmmm
但是不加滚动数组的这个只能处理1w左右的数据,否则会TLE
eg:矿工配餐
五维DP只要不是很丧病,一般就是第一维枚举位置状态,第二三维表示第一个情况的选择状态,第四五维表示第二个情况的选择状态,最终用总的状态求解
多用于求相同的两者的符合题意状态之和的最大或最小值
eg:以使得两个煤矿的产煤量的总和最大
代码:
inline int check(int a,int b,int c){
if(a==0&&b==0) return 1;
if(a==0) return 1+(b!=c);//代表如果b!=c返回1
if(a==b&&b==c) return 1;
if(a==b||b==c||a==c) return 2;
return 3;
}
int t[sz],n,f[sz][4][4][4][4];
char s[sz];
int dp(int pos,int a,int b,int x,int y){
if(pos==n+1) return 0;
if(f[pos][a][b][x][y]!=-1)
return f[pos][a][b][x][y];
return f[pos][a][b][x][y]=max(dp(pos+1,b,t[pos],x,y)+check(a,b,t[pos]),dp(pos+1,a,b,y,t[pos])+check(x,y,t[pos]));
}
int main(){
read(n);scanf("%s",s);
for(ri i=0;i<n;++i)//!从0开始读
if( s[i] == 'B' ) t[i+1] = 1;
else if( s[i] == 'M' ) t[i+1] = 2;
else t[i + 1] = 3;
memset(f,-1,sizeof(f));
cout<<dp(1,0,0,0,0);
return 0;
}
=数论=
快速幂
好东西,跑的飞快贼好的东西2333
LL sum,m,n;LL p=1e9+7;
LL pow(LL a,LL b)
{
if(b==0) return 1;if(b==1) return a%p;
LL t=(b>>1);
LL x=pow(a,t);
LL ans=( (x%p)*(x%p) )%p;
if(b&1) ans=( (ans%p)*(a%p) )%p;
return ans%p;
}
反素数
LL ans,tot,n;
LL su[13]={1,2,3,5,7,11,13,17,19,23,29,31,37};
void dfs(LL num,LL shu,LL yue,LL f)
//num是第num个质数,shu是数值,yue是约数个数,f是次方所剩数
{
if(tot<yue || (tot == yue && ans>shu) ){
tot = yue;
ans = shu;
}
LL j = 0,i = shu,div;
while(j < f){
j++;
if(n/i < su[num])
break;
div=yue * (j+1);
i=i*su[num];
if(i <= n)
dfs( num+1,i,div,j );
}
}
int main(){
read(n);
dfs(1,1,1,30);
cout<<ans;}
筛法
欧拉筛
使用广泛而且跑的贼快的筛法
int su[sz],n,m,cnt,x;bool pd[sz];
int main(){
read(n);memset(pd,1,sizeof(pd));
pd[0]=pd[1]=0;
for(ri i=2;i<=n;++i){
if(pd[i]){
cnt++;
su[cnt]=i;
}
for(ri j=1;j<=cnt;++j){
if(i*su[j]>n)//在n范围内找完了所有素数
break;
pd[i*su[j]]=0;//将确定的素数的整数倍的数全部排除
if(i%su[j]==0)
break;
}
}
for(ri i=2;i<=n;++i)
if(pd[i])
printf("%d ",i);//输出质数
}
埃氏筛(埃拉托斯特尼筛法)
跑的比欧拉筛稍微慢一点
bool vis[sz];int n;
int main()
{
cin>>n;vis[1]=1;
for(int i=2;i<=sqrt(n);++i)
if(!vis[i])
for(int j=i*i;j<=n;j+=i)
vis[j]=1;
int ans=0;
for(int i=2;i<=n;++i)
if(vis[i]==0)ans++; //是0的就是质数
cout<<ans;//输出的是质数的个数
}
gcd系列
gcd&&lcm
最大公因数最小公倍数
倒是求最小公倍数的辗转相除法挺好玩2333
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);}
int lcm(int a,int b){
return a*b/gcd(a,b);}
exgcd
来自咸鱼的绝望眼神。exgcd是用来解不定方程的东西,通过这个我们可以求得一组不定方程的解,并由此求出其他解
eg:青蛙的约会
这可确实不是个。。。emmm怎么说的东西。。。再来份详解吧。。
http://blog.csdn.net/qq_36693514/article/details/78360583
LL exgcd(LL a,LL b,LL &x,LL &y){
if(b==0){
x=1;y=0;
return a;
}
LL ans=exgcd(b,a%b,x,y);
LL temp=x;
x=y;
y=temp-a/b*y;
return ans;
}
LL cal(LL a,LL b,LL c){
LL x,y;
LL gcd=exgcd(a,b,x,y);
if(c%gcd!=0) return -1;
x*=c/gcd;//转化为a*x+b*y=c的解
b/=gcd;//约去c后原来b就变为了b/gcd;
if(b<0) b=-b;//如果b为负数就去绝对值
//x=(x%b+b)%b;(求出的就是最小正整数解)
LL ans=x%b;
if(ans<=0) ans+=b;//求最小正整数解
return ans;//返回的就是最小正整数解
}
int main(){
LL x,y,m,n,L;
while(scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&L)!=EOF)
{
LL ans=cal(m-n,L,y-x);
if(ans==-1) printf("Impossible\n");
else printf("%lld\n",ans);
}
}
费马小定理
主要是被用来求乘法逆元的
费马小定理:假如p是质数且a,p互质,那么 a^(p-1)≡1(mod p)
由此可得,a^(p-2) ≡ 1 / a (mod p),所以在mod p意义下,除以 a 等价于乘上 a^(p-2),即 a^(p-2) 为 a 的逆元
ll ksm(ll x,ll p){
if(p == 0) return 1;
if(p == 1) return x % mod;
if(p == 2) return ((x%mod) * (x%mod))%mod;
int temp = ksm(x,p/2) % mod;
if(p % 2 == 1) return (((temp * temp) % mod) * (x%mod));
if(p % 2 == 0) return (temp * temp) % mod;
}
int get(int a){
return ksm(a,mod-2);
}
CRT
int a[MAXN],b[MAXN];
LL exgcd(LL a,LL b,LL &x,LL &y)//扩展欧几里得
{
if(b==0) {
x=1;y=0;
return a;
}
else {
LL p=exgcd(b,a%b,x,y);
LL t=x;
x=y;
y=t-a/b*x;
return p;
}
}
/*x%a[i]==b[i]*/
LL China(int n)//中国剩余定理
{
LL M=1,d,x=0,y;
for(int i=1;i<=n;i++)
M*=(LL)a[i];
for(int i=1;i<=n;i++)
{
LL w=M/(LL)a[i];
exgcd(w,a[i],d,y);
x=(x+d*w*b[i])%M;
}
return (x+M)%M;
}
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]);
printf("%lld\n",China(n));
}
矩阵系列
矩阵乘法
矩阵乘法的规律:
第一个矩阵的第i行与第二个矩阵第j列对应相乘,再将这些数相加,得到一个行列数分别为第一矩阵行数,第二矩阵列数的新矩阵—>
矩阵C的行数等于矩阵A的行数,C的列数等于B的列数
eg:裸的矩阵乘法
LL jza[sz][sz],jzb[sz][sz],ans[sz][sz];
LL ai,aj,bi,bj;
int main()
{
rd(ai),rd(aj);for(ri i=1;i<=ai;++i)for(ri j=1;j<=aj;++j)rd(jza[i][j]);
rd(bi),rd(bj);for(ri i=1;i<=bi;++i)for(ri j=1;j<=bj;++j)rd(jzb[i][j]);
for(ri i=1;i<=ai;++i)
for(ri j=1;j<=bj;++j)
for(ri k=1;k<=aj;++k)
ans[i][j]+=jza[i][k]*jzb[k][j];
for(ri i=1;i<=ai;++i)
{
for(ri j=1;j<=bj;++j){
printf("%lld ",ans[i][j]);
}puts("");
}return 0;
}
矩阵快速幂+斐波那契
矩阵快速幂:利用矩阵加速线性方程
eg:裸的矩阵快速幂
LL n,k;
struct matr{
LL m[250][250];
}ans,base;
matr mult(matr a,matr b){
matr tmp;
for(ri i=1;i<=n;++i)
for(ri j=1;j<=n;++j){
tmp.m[i][j]=0;
for(ri k=1;k<=n;++k)
tmp.m[i][j]=(tmp.m[i][j]+a.m[i][k]*b.m[k][j])%mo;
}
return tmp;
}
void ksm(LL k){
for(ri i=1;i<=n;++i)
for(ri j=1;j<=n;++j)
base.m[i][j]=ans.m[i][j];
while(k){
if(k%2==1)
ans=mult(ans,base);
base=mult(base,base);
k/=2;
}
}
int main(){
rd(n),rd(k);
for(ri i=1;i<=n;++i)for(ri j=1;j<=n;++j)rd(ans.m[i][j]);
ksm(k-1);
for(ri i=1;i<=n;++i)
{
for(ri j=1;j<=n;++j)
printf("%lld ",ans.m[i][j]);
printf("\n");
}
}
应用:求斐波那契
eg:裸的矩阵乘法求斐波那契
难点在初始矩阵的构造上,不好想,对于矩阵问题涉及的很少qwq
struct matr{
LL m[2][2];
}ans,base;
LL n,k;
matr mult(matr a,matr b){
matr tmp;
for(ri i=0;i<2;++i)
for(ri j=0;j<2;++j)
{
tmp.m[i][j]=0;
for(ri k=0;k<2;++k)
tmp.m[i][j]=(tmp.m[i][j]+a.m[i][k]*b.m[k][j])%mod;
}
return tmp;
}
LL ksm(LL n){
base.m[0][0]=1,base.m[0][1]=1,base.m[1][0]=1,base.m[1][1]=0;
ans.m[0][0]=1,ans.m[0][1]=0,ans.m[1][0]=0,ans.m[1][1]=0;
while(n)
{
if(n%2==1)
ans=mult(ans,base);
base=mult(base,base);
n/=2;
}
return ans.m[0][1];
}
int main(){
while(scanf("%lld",&n))
{
if(n==-1)
break;
printf("%lld\n",ksm(n));
}return 0;
}
组合数学
ε=(′ο`*)))唉
这东西明明数学上还没学到呢_(:з」∠)_,裂上杨辉三角就冲上来变成题了
eg:16のD2T1
LL t,n,m,k,ans;LL f[sz][sz],s[sz][sz];
int main(){
read(t),read(k);
for(RI i=0;i<=2333;++i){
f[i][i]=1;
f[i][0]=1;
}
for(RI i=1;i<=2333;++i)
for(RI j=1;j<i;++j)
f[i][j]=(f[i-1][j-1]%k+f[i-1][j]%k)%k;
for(RI i=1;i<=2333;++i)
for(int j=1;j<=i;++j)
{
s[i][j]=s[i][j-1];
if(f[i][j]==0)
s[i][j]++;
}
while(t--){
read(n),read(m);ans=0;
for(LL i=1;i<=n;++i) {
LL j=min(i,m);
ans+=s[i][j];
}
write(ans),puts("");
}return 0;
}
分解质因数
rd(n);for(ri i=2;i<=n;i++)
while(n!=i)
{
if(n%i==0){
printf("%lld*",i);
n=n/i;
}
else break;
}
printf("%lld",n);
数学规律推理
首先得就是打表找规律,emmm各种打表啊什么的
大致就是Fibonacci,Catalan这样递推的
既是一个在dp中重要的推理又是在数论中重要的规律
很多数论的推理都是跟Fib挂钩,或者是Fib的变式
纯Fib的有骨牌覆盖问题
Fib类型变式的有昆虫繁殖啊什么的
=图论=
最短路(有权图)
Spfa
【模板】单源最短路
struct ed{
int nxt,to,w;
}l[sz<<1];
int dis[sz<<1],fir[sz],tot;
int n,m,s;
bool use[sz];
inline void build(int f,int t,int w){
l[++tot]=(ed){f,t,w};
l[tot].nxt=fir[f];
fir[f]=tot;
}
queue<int>q;
inline void spfa(){
q.push(s);dis[s]=0;
while(!q.empty())
{
int u=q.front();q.pop();use[u]=0;
for(ri i=fir[u];i;i=l[i].nxt){
int v=l[i].to;
if(dis[v]>dis[u]+l[i].w){
dis[v]=dis[u]+l[i].w;
if(!use[v]){
q.push(v);use[v]=1;
}
}
}
}
}
Spfa_slf
热浪
加了双端队列优化,避免被卡的尴尬(虽然也有专门卡slf的)注意头文件要加上< deque >
int n,m,s,e,fir[sz],nxt[sz<<1],dis[sz<<1],tot;
bool use[sz];
deque<int>q;
inline void spfa_slf(int s){
memset(use,0,sizeof(use));memset(dis,0x3f,sizeof(dis));
dis[s]=0;use[s]=1;q.push_front(s);
while(!q.empty()){
int u=q.front();
q.pop_front();
use[u]=0;
for(ri i=fir[u];~i;i=nxt[i]){
int v=l[i].t;
if(dis[v]>dis[u]+l[i].d){
dis[v]=dis[u]+l[i].d;
if(q.empty() || dis[v]<dis[q.front()])
q.push_front(v);
else
q.push_back(v);
use[v]=1;
}
}
}
}
dij堆优化
const int SZ=101000;const int inf=214748364;
struct ss
{
int u,dis;
bool operator < (const ss &a) const
{
return dis>a.dis;
}
};
int first[SZ<<1],nxt[SZ<<1],dis[SZ],tot=0;
bool vis[SZ];
priority_queue<ss>q;
int dij(int s,int t)
{
dis[s]=0;
q.push((ss){s,0});
while(!q.empty()){
ss now=q.top();q.pop();
if(vis[now.u]) continue;
vis[now.u]=1;
for(int i=first[now.u];i;i=nxt[i])
{
int v=es[i].t;
if(dis[v]>dis[now.u]+es[i].d){
dis[v]=dis[now.u]+es[i].d;
q.push((ss){v,dis[v]});
}
}
}
return dis[t];
}
int main(){
int n,m,s,t;int u,v,w;scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
build(u,v,w);build(v,u,w);
}
for(int i=1;i<=n;i++) dis[i]=inf;
printf("%d",dij(s,t));
}
floyd
多源最短路
用来求多源最短路,其中运用了dp的思想,但是时间复杂度高达n^3(不过写起来很快2333)
int dis[sz][sz],n,q;
int main()
{
scanf("%d",&n);for(int i = 1;i <= n;i ++)for(int j = 1;j <= n;j ++)scanf("%d",&dis[i][j]);
for(int k = 1;k <= n;k ++)
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= n;j ++)
dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]);
scanf("%d",&q);
while(q--){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",dis[a][b]);
}
}
bellman_ford
其实感觉像spfa的劣化版,或者说spfa是队列优化的bellman,二者判断负环的方式不太一样,但都是通过松弛操作判断次数决定的
int n,m,s,e;int tot,dis[sz<<1];
inline void build(int f,int t,int d){
l[++tot]=(ed){f,t,d};
}
void bellman()
{
dis[s]=0;
while(1){
bool bh=0;
for(ri i=1;i<=m*2;++i){
ed e=l[i];
if(dis[e.f]!=inf && dis[e.t]>dis[e.f]+e.d){
dis[e.t]=dis[e.f]+e.d;
bh=1;
}
}
if(!bh)
break;
}
}
int main(){
read(n),read(m),read(s),read(e);
for(ri i=1;i<=n;++i) dis[i]=inf;
for(ri i=1;i<=m;++i)
{
int f,t,v;
read(f),read(t),read(v);
build(f,t,v);
build(t,f,v);
}
bellman();
cout<<dis[e];
}
最短路(无权图)
无权图:权值相等的图 bfs,dis=层数*权值;二分图染色
建一次图,二分答案,对大于mid的边所连的两个点进行二分图染色
int n,m,ru,rv,rw,tot,s,ans;
int color[50010],first[500010],nxt[500010],w[100010];
bool flag;
int dfs(int k){
for(int i=first[k];i!=-1;i=nxt[i]) {
if(l[i].w<=s)//若当前找到的边的边权小于二分的最小值
continue; //即当前边对答案无影响-----若两端点在同一集合(监狱)内,此边权
int x=l[i].v;// 小于等于两集合内部最大值(此时二分的最小值)
if(color[x]==color[k])// -----若两端点位于不同集合,此边权
return 0;// 不会产生影响力___可行性
if(color[x]==-1)//且若用当前边进行染色可能将原本理应分在一个集合内的两点分到不同集合
{ //导致后来的染色过程发生冲突使答案偏大
color[x]=color[k]^1;//不能用来确定染色关系___必要性
if(!dfs(x))//这一部分不太理解的话可以先看下面
return 0;
}
}
return 1;
}
int check(){
for(int i=1;i<=n;i++){
if(color[i]==-1&&first[i]!=-1){
color[i]=1;
if(!dfs(i))
return 0;
}
}
return 1;
}
int main(){
memset(first,-1,sizeof(first));memset(color,-1,sizeof(color));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&ru,&rv,&rw);
w[i]=rw;//记录怨气值=可能造成的冲突影响力
build(ru,rv,rw);
build(rv,ru,rw);
}
sort(w+1,w+m+1);//从小到大排序-就可以二分啦...当然也可以从大到小.
int l=1,r=m,mid;
while(l+1<=r)//二分z市长看到的最小值-----对于同一集合内元素的怨气最大值,冲突影响力最大值
{
mid=(l+r)>>1;//二分边的编号代表最小值,而不是直接二分边权
s=w[mid]; //可以让我们取得最准确的限定条件
memset(color,-1,sizeof(color));
if(check())//染色判断是否可行
{
ans=w[mid];//统计可行答案
r=mid;
}
else l=mid+1;
}
if(l==1&&r<l)//若无轮多小的冲突影响力都不会出现,即本年内监狱中未发生任何冲突事件
printf("0");//其实没有必要写..因为这种情况下ans不会被更新值为0.
else printf("%d",ans);
}
/*为什么要用二分图染色?
题目要求分为两个集合,但对答案有影响的关系即存在于给定集合(也就是监狱)内部,也存在与集合之间
则可以自行限定另外的两个集合-----将对答案没有影响的关系(即边权小于等于二分最小值的边)
放于这两个集合内部,这些关系可以忽略,则其他对答案有影响的关系就位于集合之间,可以通过这些边染色
此时我们就得到了形如poor wyh题中的状况(二分图模型),
只不过这个状况不是直接确定的,而是由当前二分值确定的,
于是,进行二分图染色判断当前状况是否合理即能否进行这样的分配,最后二分出最小满足题目条件的解 */
第K短路
重难点在估价函数怎么写上,有的时候很不好想。在这里求第k短路,我们先从终点跑一遍到估价函数所到的点的最短路(这个最短路确定,是估价点不确定)
然后我们进行启发式最短路,跑一遍dij,
int n,m,S,E,k,h[sz];
bool use[sz],flag;
int fir[sz],nxt[sz<<1],tot,dis[sz<<1];
int fir1[sz],nxt1[sz<<1],tot1;
struct ed{
int f,t,v;
}l[sz<<1],l1[sz<<1];
inline void build(int f,int t,int v){
l[++tot]=(ed){f,t,v};nxt[tot]=fir[f];fir[f]=tot;
}
inline void build1(int f,int t,int v){
l1[++tot1]=(ed){f,t,v};nxt1[tot1]=fir1[f];fir1[f]=tot1;
}
struct A
{
int pos,g;//必须是pos在前g在后
bool operator < (const A &a) const
{
return g + h[pos]>a.g+ h[a.pos];
}
};
queue<int>q1;
void spfa(){
memset(h,0x7fffffff,sizeof(h));
q1.push(E);
h[E]=0;
while(!q1.empty()){
int u=q1.front();
use[u]=0;q1.pop();
for(ri i=fir1[u];i;i=nxt1[i]){
int v=l1[i].t;
if(h[v] > h[u] + l1[i].v){
h[v]=h[u]+l1[i].v;
if(!use[v]){
use[v]=1;q1.push(v);
}
}
}
}
}
priority_queue<A>q;
void Astar()
{
q.push((A){S,0});
int cnt=0;
while(!q.empty()){
A u=q.top();q.pop();
if(u.pos==E){
cnt++;
if(cnt==k)
{
flag=1;cout<<u.g;return ;
}
}
for(ri i=fir[u.pos];i;i=nxt[i]){
int v=l[i].t;
q.push((A){v,u.g+l[i].v});
}
}
}
int main()
{
rd(n),rd(m);for(ri i=1;i<=m;++i){
int a,b,c;rd(a),rd(b),rd(c);
build(a,b,c);build1(b,a,c);
}
rd(S),rd(E),rd(k);
if(S==E) k++;
spfa();
Astar();
if(flag==0)puts("-1");
}
差分约束
struct ed{
LL t,d;
}l[sz<<1];
int fir[sz],nxt[sz<<1],vis[sz];
LL dis[sz];
int n,x,w,e,k,tot=1;
bool use[sz],fl;
inline void build(int f,int t,int d){
l[++tot].t=t;
l[tot].d=d;
nxt[tot]=fir[f];
fir[f]=tot;
}
deque<int>q;
void spfa(int s){
q.push_back(s);
use[s]=1;
dis[s]=0;
int u,v;
while(!q.empty()) {
u=q.front();
q.pop_front();
use[u]=0;
for(ri i=fir[u];i;i=nxt[i]){
v=l[i].t;
if(dis[v]<dis[u]+l[i].d)
{
dis[v]=dis[u]+l[i].d;
vis[v]=vis[u]+1;
if(vis[v]>n){//负环vis成use,TLE2次
fl=1;return ;
}
if(!use[v]){
use[v]=1;
if(q.empty())
q.push_back(v);//没开双端TLE2次
else
v < q.front() ? q.push_front(v) : q.push_back(v);
}
}
}
}
}
int main()
{
rd(n),rd(k);
for(ri i=1;i<=k;++i)
{
rd(x);
rd(w),rd(e);
switch(x){
case(1):build(w,e,0);build(e,w,0);break;
case(2):build(w,e,1);break;
case(3):build(e,w,0);break;
case(4):build(e,w,1);break;
case(5):build(w,e,0);break;
}
}
for(ri i=1;i<=n;++i)dis[i]=-inf;
for(ri i=1;i<=n;++i)
{
if(dis[i]==-inf)
{
spfa(i);
if(fl){
puts("-1");
return 0;
}
}
}
LL ans=0;//忘了定义为0,WA一次
for(ri i=1;i<=n;++i)
ans+=dis[i];
printf("%lld",ans+n);
return 0;
}
并查集
普通版路径压缩并查集
void init(){
for(int i=1;i<=n;i++) fa[i]=i;//初始化
}
void find(int x,int y){//查询
return fa[x]==x?x:fa[x]=find(fa[x]);//路径压缩
}
void merge(int x,int y){//合并
int f1=find(x);
int f2=find(y);
if(f1!=f2) fa[f1]=f2;
}
eg:https://www.luogu.org/problem/show?pid=2024
食物链 扩展域并查集
int fa[sz],ans;int n,k,d,x,y;
int find(int x){return x==fa[x]?x:x=find(fa[x]);}
int main(){
rd(n),rd(k);for(ri i=1;i<=k*3;++i) fa[i]=i;
for(ri i=1;i<=k;++i){
rd(d),rd(x),rd(y);
if(x>n||y>n){
ans++;
continue;
}
int x1=find(x),x2=find(x+n),x3=find(x+n*2);
int y1=find(y),y2=find(y+n),y3=find(y+n*2);
if(d==1){
if(x1==y2||x2==y1) ans++;
else
fa[x1]=y1,fa[x2]=y2,fa[x3]=y3;
}
if(d==2){
if(x1==y1||x1==y2) ans++;
else
fa[x1]=y3,fa[x2]=y1,fa[x3]=y2;
}
}we(ans);
}
最小生成树
Krusal
适用于稀疏图,而Prim适合稠密图 eg:繁忙的都市
http://www.lydsy.com/JudgeOnline/problem.php?id=1083
int fa[sz],n,m,ans;
struct ed{
int f,t,v;
}l[sz<<1];
int find(int x){return x==fa[x]?x:x=find(fa[x]);}
void kru(){
for(ri i=1;i<=n;++i)
{
int x=find(l[i].f),y=find(l[i].t);
if(x!=y){
ans=l[i].v;
fa[x]=y;
}
}
}
int main(){
cin>>n>>m;for(ri i=1;i<=n;++i) fa[i]=i;
for(ri i=1;i<=m;++i)
cin>>l[i].f>>l[i].t>>l[i].v;
kru();
cout<<n-1<<' '<<ans;
}
拓扑排序
http://blog.csdn.net/qq_36693514/article/details/77816633主要关键是按照特定顺序,也就是出度入度来遍历,很有效的一种遍历方式,经常和dp放在一起考emmmm,比如bzoj上的食物链问题,就是一个很典型的拓扑(虽然没看出来dp)
http://blog.csdn.net/qq_36693514/article/details/78319010
queue<int>q;
int n,m,t,ru[sz],a[sz],b[sz],tot,cnt;
int fir[sz],nxt[sz],l[sz];
void top_sort()
{
for(ri i=1;i<=n;++i)
if(!ru[i]){
q.push(i);
// cout<<i; 求拓扑序列
}
while(!q.empty()){
int u=q.front();q.pop();cnt++;
for(ri i=fir[u];i;i=nxt[i]){
ru[l[i]]--;
if(!ru[l[i]]){
// cout<<l[i];
q.push(l[i]);
}
}
}
if(cnt>=n)cout<<"o(∩_∩)o";
else {
cout<<"T_T"<<'\n';
cout<<n-cnt;
}
}
int main(){
cin>>n>>m;
for(ri i=1;i<=m;++i){
int f,t;
cin>>f>>t;
ru[t]++;build(f,t);
}
top_sort();
}
LCA
eg:小机房的树
int n,f,t,v;int m,x,y;LL rak[sz];
int fa[sz][21],dep[sz];int fir[sz],nxt[sz<<1],tot;
inline void btree(int x,int f,int v){
dep[x]=dep[f]+1;fa[x][0]=f;rak[x]=v;
for(ri i=1;i<=20;++i)
fa[x][i]=fa[fa[x][x-1]][i-1];
for(ri i=fir[x];~i;i=nxt[i])
if(l[i].t!=f)
btree(l[i].t,x,v+l[i].v);
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
int c=dep[x]-dep[y];
for(ri i=20;i>=0;--i){
if(c & (1<<i))
x=fa[x][i];
}
if(x==y) return x;
for(ri i=20;i>=0;--i)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main(){
memset(fir,-1,sizeof(fir));
cin>>n;for(ri i=1;i<n;++i){
cin>>f>>t>>v;
build(f,t,v),build(t,f,v);
}
btree(0,0,0);
read(m);while(m --){
read(x);read(y);
printf("%d\n",rank[x] + rank[y] - (rank[lca(x,y)] << 1));
}
}
tarjan
eg:爱在心中
int fir[sz],nxt[sz<<1],tot;
int ttot,dfn[sz],low[sz];
int stack[sz],size[sz],scc[sz];
bool in[sz];
int n,m,a,b,snum,cnt,du[sz];
struct ed{
int f,t;
}l[sz<<1];
void tarjan(int u){
dfn[u]=low[u]=++ttot;
stack[++snum]=u;
in[u]=1;
for(ri i=fir[u];~i;i=nxt[i]){
int v=l[i].t;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);//low[v]!!,并非为dfn[v]
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
cnt++;
while(stack[snum+1]!=u){
scc[stack[snum]]=cnt;
in[stack[snum]]=0;
size[cnt]++;
snum--;
}
}
}
int main()
{
rd(n),rd(m);
memset(fir,-1,sizeof(fir));
for(ri i=1;i<=m;++i) {
rd(a),rd(b);
build(a,b);
}
for(ri i=1;i<=n;++i)
if(!dfn[i])
tarjan(i);
for(ri i=1;i<=n;++i)
for(ri j=fir[i];~j;j=nxt[j])
{
int v=l[j].t;
if(scc[i]!=scc[v])
du[scc[i]]++;
}
int sum1=0,sum2=0,x;
for(ri i=1;i<=cnt;++i){
if(size[i]>1)
sum1++;
if(!du[i]){
sum2++;
x=i;
}
}
printf("%d\n",sum1);
if(sum2==1&&size[x]!=1){
for(ri i=1;i<=n;++i)
if(scc[i]==x)
printf("%d ",i);
}
else puts("-1");
}
=数据结构=
哈希
将符赋值成数,成为n进制下各种问题,在这种前提下对于符的问题解决更方便
char s[sz];
int mod,l,k,v[sz];
void hash(){
v[l]=1;
for(ri i=l-1;i>0;--i){
v[i]=v[i+1]*26;
v[i]%=mod;
}
k=0;
for(ri i=1;i<=l;++i){
k*=26;
k+=s[i]-'A';
k%=mod;
}
}
inline int swwap(int i,int j){
int sum=k;
sum-=(v[i]*(s[i]-'A'))%mod;
sum-=(v[j]*(s[j]-'A'))%mod;
sum+=(v[i]*(s[j]-'A'))%mod;sum%=mod;
sum+=(v[j]*(s[i]-'A'))%mod;sum%=mod;
return sum;
}
inline void check(){
if(k%mod==0){
puts("0 0");
return;
}
for(ri i=1;i<l;++i)
for(ri j=i+1;j<=l;++j)
{
if(swwap(i,j)%mod==0){
printf("%d %d\n",i,j);
return;
}
}
puts("-1 -1");
}
int main()
{
scanf("%s %d",s+1,&mod);
l=strlen(s+1);
hash();
check();
}
线段树
#define L(x) (x << 1)//左儿子
#define R(x) (x << 1 | 1)//右儿子
#define sz(x) (tree[x].r - tree[x].l + 1)//区间大小
using namespace std;
const long long MAXN = 200000 + 5;
long long num[MAXN];
// l == 左;r == 右;p == now;
struct dot{
long long l,r;
long long add,sum;//修改求和
long long min,max;//最值标记
bool xczhw;//没用打着玩……这是个梗QAQ
}tree[MAXN << 2];
//标记下放们 p == now
void update(long long p){
tree[p].sum = tree[L(p)].sum + tree[R(p)].sum;
tree[p].max = max(tree[L(p)].max,tree[R(p)].max);
tree[p].min = min(tree[L(p)].min,tree[R(p)].min);
}
void spread(long long p){
if(!tree[p].add) return;
tree[L(p)].max += tree[p].add;
tree[R(p)].max += tree[p].add;
tree[L(p)].min += tree[p].add;
tree[R(p)].min += tree[p].add;
tree[L(p)].add += tree[p].add;
tree[R(p)].add += tree[p].add;
tree[L(p)].sum += tree[p].add * sz(L(p));
tree[R(p)].sum += tree[p].add * sz(R(p));
tree[p].add = 0;//!!!!!!!
update(p);
}//简单的建立
void build(long long l,long long r,long long p){
tree[p].l = l,tree[p].r = r;
if(l == r)
{
tree[p].min = tree[p].max = tree[p].sum = num[l];
return;
}
long long mid = (tree[p].l + tree[p].r) >> 1;
build(l,mid,L(p));
build(mid+1,r,R(p));
update(p);
}
//区间修改
void change(long long l,long long r,long long p,long long v){
if(l <= tree[p].l && tree[p].r <= r)
{
tree[p].add += v;
tree[p].min += v;
tree[p].max += v;
tree[p].sum += v*sz(p);
return;
}
spread(p);
long long mid = (tree[p].l + tree[p].r) >> 1;
if(l <= mid) change(l,r,L(p),v);
if(mid < r) change(l,r,R(p),v);
update(p);
}
//区间求和
long long ask_sum(long long l,long long r,long long p){
if(l <= tree[p].l && tree[p].r <= r)
return tree[p].sum;
spread(p);
long long ans = 0,mid = (tree[p].l + tree[p].r) >> 1;
if(l <= mid) ans += ask_sum(l,r,L(p));
if(mid < r) ans += ask_sum(l,r,R(p));
update(p);
return ans;
}
//区间最大值
long long ask_max(long long l,long long r,long long p){
if(l <= tree[p].l && tree[p].r <= r)
return tree[p].max;
spread(p);
long long ans = 0,mid = (tree[p].l + tree[p].r) >> 1;
if(l <= mid) ans = max(ans,ask_max(l,r,L(p)));
if(mid < r) ans = max(ans,ask_max(l,r,R(p)));
update(p);
return ans;
}
//区间最小值
long long ask_min(long long l,long long r,long long p){
if(l <= tree[p].l && tree[p].r <= r)
return tree[p].min;
spread(p);
long long ans = 2333333,mid = (tree[p].l + tree[p].r) >> 1;
if(l <= mid) ans = min(ans,ask_min(l,r,L(p)));
if(mid < r) ans = min(ans,ask_min(l,r,R(p)));
update(p);
return ans;
}
long long n,m,a,b,c;string t;
int main()
{
memset(num,0,sizeof(num));
scanf("%lld",&n);
for(long long i = 1; i <= n; i ++) scanf("%lld",&num[i]);
build(1,n,1);
scanf("%lld",&m);
for(long long i = 1; i <= m; i ++){
cin >> t;
if(t == "add"){
scanf("%lld %lld %lld",&a,&b,&c);
change(a,b,1,c);
}
else if(t == "sum") {
scanf("%lld %lld",&a,&b);
printf("%lld\n",ask_sum(a,b,1));
}
else if(t == "max") {
scanf("%lld %lld",&a,&b);
printf("%lld\n",ask_max(a,b,1));
}
else if(t == "min") {
scanf("%lld %lld",&a,&b);
printf("%lld\n",ask_min(a,b,1));
}
}
}
扫描线算法
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l+q[i].r)>>1)
typedef long long ll;
const int inf = 10000000 + 10;
const int sz = 111;
using namespace std;
double x[sz<<1];
struct edge{
double l,r;
double h;
int f;
}e[sz<<1];
bool cmp(edge a,edge b){return a.h<b.h;}
struct node{
int l,r;
int s;
double len;
}q[sz<<3];
void build(int i,int l,int r){
q[i].l=l,q[i].r=r;
q[i].s=0,q[i].len=0;
if(l==r) return;
int mid=m(i);
build(ls,l,mid);
build(rs,mid+1,r);
}
void up(int i){
if(q[i].s) q[i].len=x[q[i].r+1]-x[q[i].l];
else if(q[i].l==q[i].r) q[i].len=0;
else q[i].len=q[ls].len+q[rs].len;
}
void update(int i,int l,int r,int xx){
if(q[i].l==l&&q[i].r==r)
{
q[i].s+=xx;up(i);
return;
}
int mid=m(i);
if(r<=mid) update(ls,l,r,xx);
else if(l>mid) update(rs,l,r,xx);
else{
update(ls,l,mid,xx);update(rs,mid+1,r,xx);
}
up(i);
}
int n,kas;
int main(){
while(scanf("%d",&n)==1&&n){
int tot=0;
for(int i=0;i<n;++i){
double x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
edge &t1=e[tot];edge &t2=e[1+tot];
t1.l=t2.l=x1,t1.r=t2.r=x2;
t1.h=y1;t1.f=1;
t2.h=y2;t2.f=-1;
x[tot]=x1;x[tot+1]=x2;tot+=2;
}
sort(e,e+tot,cmp);
sort(x,x+tot);
int k=1;
for(int i=1;i<tot;++i)
if(x[i]!=x[i-1])
x[k++]=x[i];
build(1,0,k-1);
double ans=0.0;
for(int i=0;i<tot;++i){
int l=lower_bound(x,x+k,e[i].l)-x;
int r=lower_bound(x,x+k,e[i].r)-x-1;
update(1,l,r,e[i].f);
ans+=(e[i+1].h-e[i].h)*q[1].len;
}
cout<<"Test case #"<<++kas<<endl;
printf("Total explored area: %.2f\n\n",ans);
} return 0;
}
二维前缀和
eg:射命丸文
对于一次的查询答案ans应该等于s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1],一个大的区域减去两个较小区域再加上被多减出去的那个区域,剩下的右下角小区块就是我们要求的区域
int num[sz][sz];int s[sz][sz];int n,m,r,c,ans;
int main()
{
cin>>n>>m>>r>>c;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
cin>>num[i][j];
s[i][j]=s[i-1][j]+s[i][j-1]+num[i][j]-s[i-1][j-1];
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
ans=max(ans,s[min(i+r-1,n)][min(j+c-1,m)]-s[min(i+r-1,n)][j-1]-s[i-1][min(j+c-1,m)]+s[i-1][j-1]);
cout<<ans<<'\n';
}
栈/队列
栈的STL实现,,,好像还没掌握清楚w
先说说栈维护表达式运算吧w
这些都是emmmm
http://www.tyvj.cn/p/1041
http://www.tyvj.cn/p/1042
http://codevs.cn/problem/2178/
给出一个表达式,其中运算符仅包含+,-,*,/,^要求求出表达式的最终值数据可能会出现括号情况 还有可能出现多余括号情况
数据保证不会出现>maxlongint的数据。数据可能会出现负数情况
char ch[10005];LL num[10005], top1;LL opt[10005], top2;
void get_num(){
if (!top2) return;
LL y = num[top1--], x = num[top1--];
LL pos = opt[top2--];
if (pos == 5) x = pow(x, y);if (pos == 4) x = x / y;if (pos == 3) x = x * y;
if (pos == 2) x = x - y;if (pos == 1) x = x + y;
num[++top1] = x;
}
int main(){
scanf("%s", ch);
LL n = strlen(ch);
for (int i = 0; i < n;) {
if (ch[i] >= '0' && ch[i] <= '9')
{
LL x = 0;
while(ch[i] >= '0' && ch[i] <= '9'){
x = x * 10 + ch[i] - '0';
i++;
}
num[++top1] = x;
}
if (ch[i] == '('){
opt[++top2] = 0;
i++;
}
if (ch[i] == '+') {
while(top2 && opt[top2] != 0) get_num();
opt[++top2] = 1; i++;
}
if (ch[i] == '-'){
if (i == 0 || ch[i - 1] == '(')
{
int x = 0; i++;
while(ch[i] >= '0' && ch[i] <= '9')
{
x = x * 10 - (ch[i] - '0');
i++;
}
num[++top1] = x;
}
else{
while(top2 && opt[top2] != 0)
get_num();
opt[++top2] = 2;
i++;
}
}
if (ch[i] == '*'){
while(top2 && opt[top2] > 2)
get_num();
opt[++top2] = 3;
i++;
}
if (ch[i] == '/'){
while(top2 && opt[top2] > 2)
get_num();
opt[++top2] = 4;
i++;
}
if (ch[i] == '^'){
while(top2 && opt[top2] > 4)
get_num();
opt[++top2] = 5;
i++;
}
if (ch[i] == ')'){
while(top2 && opt[top2] != 0)
get_num();
top2--;
i++;
}
}
while(top2){
if (opt[top2] == 0)
top2--;
else get_num();
}
printf("%lld\n", num[top1]);
}
图的遍历(BFS版)
主要是找到一种既不重复又不遗漏的访问方法,可用来判断一个图是否连通,可用于穷举求解,可解决回溯问题.从图中某个顶点V0出发,并在访问此顶点后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到;
若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点;
重复上述过程,直至图中所有顶点都被访问到为止
BFS所搜索到的都是最短的路径
void bfs(int k){
int v[N]={0},i; //N表示图的顶点个数
queue<int> q;
v[k]=1; //访问标记为1
q.push(k);
while(!q.empty()){
k=q.front();
q.pop();
for(i=0;i<N;i++)
if(a[k][i]==1&&!v[i]){ //a[k][i]表示顶点k,和i有关系
q.push(i);
v[i]=1;
}
}
}
树链剖分
这个。。。倒真是没太用过。。。相信着暴力出奇迹w(什么鬼)
树链剖分笔记详解
#define ls p<<1
#define rs p<<1|1
#define mid (shu[p].l+shu[p].r >>1)
#define RI register int
using namespace std;
const int sz = 300010;
int pos[sz],son[sz],top[sz],size[sz];int dep[sz],fa[sz],fir[sz],nxt[sz];
int w[sz],tot,n,q,cnt,x,y,num;
char ch[15];
struct node{
int l,r;
int sum,max;
}shu[sz<<2];
struct ed{
int f,t;
}l[sz<<1];
inline void add(int f,int t){
l[++tot]=(ed){f,t};
nxt[tot]=fir[f];
fir[f]=tot;
}
inline void build(int p,int l,int r){
shu[p].l=l,shu[p].r=r;
if(l==r){
shu[p].sum = shu[p].max=0;
return;
}
int midd = l+r>>1;
build(ls,l,midd);
build(rs,midd+1,r);
shu[p].sum=shu[ls].sum+shu[rs].sum;
shu[p].max=max(shu[ls].max,shu[rs].max);
}
void dfs1(int k,int f,int d)
{
dep[k]=d;fa[k]=f;size[k]=1;
for(RI i=fir[k];i;i=nxt[i]){
int x=l[i].t;
if(x==f) continue;
dfs1(x,k,d+1);
size[k]+=size[x];
if(!son[k]||size[x]>size[son[k]])
son[k]=x;
}
}
void dfs2(int k,int tot){
top[k]=tot;
pos[k]=++num;
if(!son[k]) return;
dfs2(son[k],tot);
for(RI i=fir[k];i;i=nxt[i]){
int x=l[i].t;
if(x!=son[k]&&x!=fa[k])
dfs2(x,x);
}
}
void in(int p,int x,int v){
if(shu[p].l==shu[p].r){
shu[p].sum=shu[p].max=v;
return;
}
if(x<=mid) in(ls,x,v);
else if(x>mid) in(rs,x,v);
shu[p].sum=shu[ls].sum+shu[rs].sum;
shu[p].max=max(shu[ls].max,shu[rs].max);
}
int qsum(int p,int l,int r)
{
if(shu[p].l>=l&&shu[p].r<=r)
return shu[p].sum;
int ans=0;
if(l<=mid) ans+=qsum(ls,l,r);
if(r>mid) ans+=qsum(rs,l,r);
return ans;
}
int qmax(int p,int l,int r)
{
if(shu[p].l>=l&&shu[p].r<=r)
return shu[p].max;
int ans=-10000007;
if(l<=mid) ans=max(ans,qmax(ls,l,r));
if(r>mid) ans=max(ans,qmax(rs,l,r));
return ans;
}
int dosum(int x,int y)
{
int ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])
swap(x,y);
ans+=qsum(1,pos[top[x]],pos[x]);
x=fa[top[x]];
}
if(pos[x]>pos[y]) swap(x,y);
ans+=qsum(1,pos[x],pos[y]);
return ans;
}
int domax(int x,int y)
{
int ans=-1e8;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])
swap(x,y);
ans=max(ans,qmax(1,pos[top[x]],pos[x]));
x=fa[top[x]];
}
if(pos[x]>pos[y])swap(x,y);
ans=max(ans,qmax(1,pos[x],pos[y]));
return ans;
}
int main()
{
read(n);
for(RI i=1;i<n;i++){
read(x),read(y);
add(x,y),add(y,x);
}
for(RI i=1;i<=n;i++) read(w[i]);
dfs1(1,0,1);dfs2(1,1);
build(1,1,n);
for(RI i = 1;i <= n;i ++)in(1,pos[i],w[i]);
read(q);
while(q--){
scanf("%s",ch);
read(x),read(y);
if(ch[0]=='C'){
w[x]=y;
in(1,pos[x],y);
}
else{
if(ch[1]=='M')
printf("%d\n",domax(x,y));
else
printf("%d\n",dosum(x,y));
}
}
}
字符串
Kmp
int len1,len2,tot;int Next[100010],pos[100010];char s[1000010],t[100010];
void initNext(){
int j=0;
for(int i=2;i<=len2;i++)
{
while(j&&t[i]!=t[j+1])
j=Next[j];
if(t[i]==t[j+1])
j++;
Next[i]=j;
}
}
/*void initNext(){//从0开始存
int j=-1;
Next[0]=-1;
for(int i=1;i<len;i++){
while(j!=-1&&t[i]!=t[j+1])
j=Next[j];
if(t[i]==t[j+1])
j++;
Next[i]=j;
}
}*/
int KMP(){
int j=0,ans=0;
for(int i=1;i<=len1;i++){
while(j&&s[i]!=t[j+1])
j=Next[j];
if(s[i]==t[j+1])
j++;
if(j==len2)
{
//ans++;
pos[++tot]=i;
j=Next[j];
}
}
//return ans;
}
int main(){
scanf("%s%s",s+1,t+1);
len1=strlen(s+1);len2=strlen(t+1);
initNext();
KMP();
for(int i=1;i<=tot;i++)
printf("%d\n",pos[i]-len2+1);
for(int i=1;i<=len2;i++)
printf("%d ",Next[i]);
}
其他算法
随机化
const int SIZE=10010;const int INF=233333333;
int num[SIZE];int sum1=0,sum2=INF,n;
void randnum(){//随机化
for(int i=1;i<=n;i++){
int x=rand()%n+1;
while(x==i)
x=rand()%n+1;
swap(num[i],num[x]);
}
}
void getans(){//贪心
int ans1=num[1],ans2=0,t1=1,t2=0;
for(int i=2;i<=n;i++) {
if(t1==t2-1)ans1+=num[i],t1++;
else ans2+=num[i],t2++;
}
if(ans1>ans2)
swap(ans1,ans2);
if(abs(ans1-ans2)<abs(sum1-sum2))
sum1=ans1,sum2=ans2;
}
int main()
{
srand(time(0));scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&num[i]);
for(int i=1;i<=100000;i++){
getans();randnum();
}
printf("%d %d",sum1,sum2);
}
STL大法
sort+unique
eg:明明的随机数unique返回值是指针,指向去重后序列最后一项的后一项,要输出值的话需要减去位置下标
重复的元素会被放到后面,只能去掉连续的重复元素,要先排序
int main(){
cin>>n;for(int i=1;i<=n;++i) cin>>a[i];
sort(a+1,a+n+1);
int m=unique(a+1,a+n+1)-a-1;
cout<<m<<endl;
for(int i=1;i<=m;++i) cout<<a[i]<<' ';
}