NOIp2017 题解

Day1

T1 小凯的疑惑

题目传送门

考场上打表搞了一个很奇怪的结论,化简后就是 a ∗ b − a − b a*b-a-b abab。具体证明现在还是不大会。

代码:

#include
#include
#include
using namespace std;
typedef long long LL;
LL a,b;
int main(){
    scanf("%lld%lld",&a,&b);
    if (a>b) swap(a,b);
    if (a==1){
        printf("0\n");
        return 0;
    }
    if (a==2){
        printf("%lld\n",b-2);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    LL ans=(4+(a-3)*2+4)*(a-2)/2+1;
    ans+=(b-a-1)*(a-1);
    printf("%lld\n",ans);
    return 0;
}

T2 时间复杂度

题目传送门

一般用一个栈模拟,细节比较多。没什么好说的。

考场上写的代码只会统计第一个循环。。。然而还是有80。

当时太紧张就写得很丑。

代码:

#include
#include
#include
#include
#define MAXN 100
using namespace std;
struct Orz{
    char c;
    bool f;
}stack[MAXN+5];
int top,t,l,cmp,a[MAXN+5],ma;
int f;
char s[MAXN+5],sc[MAXN+5],sx[MAXN+5],sy[MAXN+5];
int main(){
    scanf("%d",&t);
    while (t--){
        memset(a,0,sizeof(a));
        f=0; top=0; cmp=0; ma=0;
        scanf("%d%s",&l,s);
        if (s[2]=='n') f=1;
        for (int i=0;s[i];i++)
            if (s[i]>='0'&&s[i]<='9')
                cmp=cmp*10+s[i]-48;
        int num=0;
        bool flag=false,fa=false,fl=false; if (l&1) flag=true;
        for (int i=1;i<=l;i++){
            int ff=0; char ss[2];
            scanf("%s",ss);
            ff=ss[0];
            if (ff=='F'){
                int c,x=0,y=0;
                scanf("%s%s%s",sc,sx,sy);
                c=sc[0];
                for (int j=0;sx[j];j++)
                    if (sx[j]>='0'&&sx[j]<='9')
                        x=x*10+sx[j]-48;
                for (int j=0;sy[j];j++)
                    if (sy[j]>='0'&&sy[j]<='9')
                        y=y*10+sy[j]-48;
                if (sx[0]=='n') x='n';
                if (sy[0]=='n') y='n';
                if (flag) continue;
                if (a[c-'a']){ flag=true; continue; }
                if (x=='n'&&y!='n') fl=true;
                if (x!='n'&&y!='n'&&x>y) fl=true;
                if (x!='n'&&y=='n') num++;
                if (x!='n'&&y=='n'&&(!fl)) fa=true,ma=max(ma,num);
                a[c-'a']++; 
                Orz p; p.c=c;
                if (x!='n'&&y=='n') p.f=true;
                else p.f=false;
                stack[++top]=p;
            }
            else{
                if (!top) { flag=true; continue; }
                if (stack[top].f) num--;
                a[stack[top--].c-'a']--;
                if (!top) fl=false;
            }
        }
        if (top) flag=true;
        if (flag){
            printf("ERR\n");
            continue;
        }
        if ((fa&&(f==0))||(ma!=cmp&&(f==1))){
            printf("No\n");
            continue;
        }
        printf("Yes\n");
    }
    return 0;
}

T3 逛公园

题目传送门

最短路+记忆化搜索

考场上用最短路优化一下就有60了。。。

先刷一遍反图最短路求出每个点到 n n n的最短路 d i s [ i ] dis[i] dis[i]

f [ i ] [ j ] f[i][j] f[i][j]表示到第 i i i个点,超出最短路的路程 ≤ j \leq j j的方案数。搜索的时候对每条边的转移 f [ i ] [ j ] = ∑ f [ v ] [ j − d ] f[i][j]=\sum f[v][j-d] f[i][j]=f[v][jd]。其中 d = d i s [ v ] − d i s [ i ] + w d=dis[v]-dis[i]+w d=dis[v]dis[i]+w。判0环的话就记录一下当前状态有没有在访问路径上即可。

代码:

#include
#include
#include
#include
#include
#define N 100005
#define F inline
using namespace std;
struct P{ int x,d; };
struct edge{ int nxt,to,d; }ed[N<<2];
int n,m,k,p,K,d[N],h1[N],h2[N],f[N][55];
priority_queue

q; bool v[N][55]; F char readc(){ static char buf[100000],*l=buf,*r=buf; if (l==r) r=(l=buf)+fread(buf,1,100000,stdin); return l==r?EOF:*l++; } F int _read(){ int x=0; char ch=readc(); while (!isdigit(ch)) ch=readc(); while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc(); return x; } #define add(h,x,y,z) ed[++k]=(edge){h[x],y,z},h[x]=k F bool operator <(P a,P b){ return a.d>b.d; } F void Dij(){ while (!q.empty()) q.pop(); for (q.push((P){n,d[n]=0});!q.empty();q.pop()){ int x; if (q.top().d>d[x=q.top().x]) continue; for (int i=h2[x],v;i;i=ed[i].nxt) if (d[v=ed[i].to]>d[x]+ed[i].d) d[v]=d[x]+ed[i].d,q.push((P){v,d[v]}); } } int dfs(int x,int k){ if (v[x][k]) return -1; if (f[x][k]) return f[x][k]; v[x][k]=true,f[x][k]+=(x==n); for (int i=h1[x],v,w;i;i=ed[i].nxt) if ((w=d[v=ed[i].to]-d[x]+ed[i].d)<=k) if (dfs(v,k-w)==-1) return -1; else (f[x][k]+=f[v][k-w])%=p; return v[x][k]=false,f[x][k]; } int main(){ for (int t=_read();t;t--){ n=_read(),m=_read(),K=_read(),p=_read(),k=0; for (int i=1;i<=n;i++) h1[i]=h2[i]=0,d[i]=1e9; for (int i=1;i<=n;i++) for (int j=0;j<=K;j++) f[i][j]=v[i][j]=0; for (int i=1,x,y,z;i<=m;i++){ x=_read(),y=_read(),z=_read(); add(h1,x,y,z),add(h2,y,x,z); } Dij(),printf("%d\n",dfs(1,K)); } return 0; }

Day2

T1 奶酪

题目传送门

并查集/BFS都可以。本来会爆long long的但是出题人没有卡。判断的时候移个项就好了

代码:

#include
#include
#include
#include
#define MAXN 1000
#define sqr(x) ((x)*(x))
using namespace std;
typedef long long LL;
struct edge{
    int next,to;
}ed[MAXN*MAXN*6+5];
struct node{
    LL x,y,z;
}a[MAXN*2+5];
int t,n,k;
LL m,r;
int h[MAXN*2+5],que[MAXN*2+5];
bool f[MAXN*2+5];
void addedge(int x,int y){
    ed[++k].next=h[x]; ed[k].to=y; h[x]=k;
}
LL calc(node a,node b){
    return sqr(a.x-b.x)+sqr(a.y-b.y)+sqr(a.z-b.z);
}
bool bfs(int s,int t){
    memset(f,false,sizeof(f));
    int r=0,w=1; que[1]=s; f[s]=true;
    while (r=m) addedge(i,MAXN+1); 
        }
        for (int i=1;i

T2 宝藏

题目传送门

状压DP

为什么我会觉得prim是对的!为什么我70分的暴力都不打!果然我好菜

我是不会告诉你我不知道怎么枚子集的

n=12就是状压的范围。据说标算 O ( 4 n ) ​ O(4^n)​ O(4n)我不会啊,优化过的 O ( n 3 n ) ​ O(n3^n)​ O(n3n)我也不会啊,我只会 O ( n 2 3 n ) ​ O(n^23^n)​ O(n23n)不管了能过就行

Orz写模拟退火的

f [ i ] [ j ] [ s ] f[i][j][s] f[i][j][s]表示以 i i i为根的子树中, i i i的深度的为 j j j,子树中的节点集合为 s s s的最小代价。那么有 f [ i ] [ j ] [ s ] = m i n { f [ k ] [ j + 1 ] [ s s ] + f [ i ] [ j ] [ s − s s ] + a [ i ] [ k ] } f[i][j][s]=min\{f[k][j+1][ss]+f[i][j][s-ss]+a[i][k]\} f[i][j][s]=min{f[k][j+1][ss]+f[i][j][sss]+a[i][k]},其中 s s ss ss s s s的子集, k k k s s ss ss内的一个点。时间复杂度为 O ( n 3 3 n ) O(n^33^n) O(n33n)

把转移方程中与 s s s无关的提取出来,预处理 g [ i ] [ j ] [ s ] = m i n { f [ k ] [ j + 1 ] [ s ] + a [ i ] [ k ] } g[i][j][s]=min\{f[k][j+1][s]+a[i][k]\} g[i][j][s]=min{f[k][j+1][s]+a[i][k]},这样就是 O ( n 2 3 n ) O(n^23^n) O(n23n)的了。

代码:

#include
#include
#include
#define N 15
#define M (1<<12)+1
using namespace std;
int n,m,ans=1e9,a[N][N],f[N][N][M],g[N][N][M];
int main(){
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++){
			a[i][j]=1e9;
			for (int s=1;s<1<

T3 列队

题目传送门

线段树动态开点

去年不会动态开点啊。。。
建n+1棵树,前n棵维护当前这行(除了最后一列),第n+1棵维护最后一列。每次操作的时候分操作的列是不是最后一列讨论一下就好了。
细节有点多,调了好久最后还是借鉴了一下题解

代码:

#include
#include
#include
#include
#define N 300005
#define F inline
using namespace std;
typedef long long LL;
struct tree{ int ls,rs,sz; LL x; }t[N<<6];
int n,m,q,mx,nd,rt[N],p[N]; LL ans,tmp;
F char readc(){
	static char buf[100000],*l=buf,*r=buf;
	if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
	return l==r?EOF:*l++;
}
F int _read(){
	int x=0; char ch=readc();
	while (!isdigit(ch)) ch=readc();
	while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
	return x;
}
F void writec(LL x){ if (x>9) writec(x/10); putchar(x%10+48); }
#define pd(l,r,f) (f==n+1?max(min(r,n)-l+1,0):max(min(r+1,m)-l,0))
F void nsrt(int &x,int l,int r,int p,LL w,int f){
	if (!x) t[x=++nd].sz=pd(l,r,f); int mid=l+r>>1;
	t[x].sz++; if (l==r) return void(t[x].x=w);
	if (p<=mid) nsrt(t[x].ls,l,mid,p,w,f);
	else nsrt(t[x].rs,mid+1,r,p,w,f);
}
F LL srch(int &x,int l,int r,int w,int f){
	if (!x) t[x=++nd].sz=pd(l,r,f); int mid=l+r>>1,p; t[x].sz--;
	if (l==r) return t[x].x?t[x].x:t[x].x=(f<=n?1ll*(f-1)*m+l:1ll*l*m);
	if (w<=(p=t[x].ls?t[t[x].ls].sz:mid-l+1))
		return srch(t[x].ls,l,mid,w,f);
	else return srch(t[x].rs,mid+1,r,w-p,f);
}
F LL calc(int x,int y){
	LL ans,tmp=srch(rt[n+1],1,mx,x,n+1);
	ans=y!=m?srch(rt[x],1,mx,y,x):tmp;
	if (y!=m) nsrt(rt[x],1,mx,++p[x],tmp,x);
	return nsrt(rt[n+1],1,mx,++p[n+1],ans,n+1),ans;
}
int main(){
	n=_read(),m=_read(),q=_read(),mx=max(n,m)+q;
	for (int i=1;i<=n+1;i++) p[i]=(i==n+1?n:m-1);
	for (int x,y;q;q--) x=_read(),y=_read(),writec(calc(x,y)),puts("");
	return 0;
}

NOIP2018要苟住啊

UPD 2018.11.11 假的,要退役了

你可能感兴趣的:(算法/总结/游记,蒟蒻zxl的Blog专栏)