HGOI 20180817

首先祝各位节日快乐

T1 CF359A

斯波题,若四条边界上有1,直接输出2,否则输出4。好玩的是我还成功误导了某位同学,使他误以为是DFS(笑)。


#include<bits/stdc++.h>
#define N 101
using namespace std;
int n,m;
int main(){
	freopen("table.in","r",stdin);
    freopen("table.out","w",stdout);
	cin>>n>>m;
	int flag=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int x;
			cin>>x;
			if (x)
				if (i==1||i==n||j==1||j==m) flag=1;
		}
	}
	if (flag) cout<<2; else cout<<4;
	return 0;
}

T2 CF17C

考场上愣是没看出来DP,惨遭20。其实正解也不难,见下。

将连续的相同字符压缩为同一个,

例如aabbaacc即为abac

我们可以发现,字母的顺序是不会换的,比如abac就不可能变换为abca。

所以就可以用类似背包的思想,f[i][x][y][z]代表最后一个选的字符是原串第i位,用了x个a y个b z个c的答案。

方程就是

f[nex[i][0]][a+1][b][c]=(f[nex[i][0]][a+1][b][c]+f[i][a][b][c]);
f[nex[i][1]][a][b+1][c]=(f[nex[i][1]][a][b+1][c]+f[i][a][b][c]);
f[nex[i][2]][a][b][c+1]=(f[nex[i][2]][a][b][c+1]+f[i][a][b][c]);

其中next[i][0..2]为预处理原串字符i后面(包括自身)最近的’a”b”c’的位置。

代码如下


#include<bits/stdc++.h>
#define ll long long
#define mod 51123987
using namespace std;
int n,nex[210][3],stand;
ll f[155][55][55][55],ans;
char c[155];

bool check(int a,int b,int c){
	return (a+b+c==n)&&(fabs(a-b)<=1)&&(fabs(c-b)<=1)&&(fabs(a-c)<=1);
}

int main(){
	freopen("balance.in", "r", stdin);
  	freopen("balance.out", "w", stdout);
	cin>>n;
	cin>>c;
	nex[n+1][0]=nex[n+1][1]=nex[n+1][2]=n+1;
	for(int i=n;i>=1;i--){
		nex[i][0]=nex[i+1][0],nex[i][1]=nex[i+1][1],nex[i][2]=nex[i+1][2];
		nex[i][c[i-1]-'a']=i;
	}
	ans=0,stand=(n+2)/3,f[1][0][0][0]=1;
	for(int i=1;i<=n;i++)
		for(int a=0;a<=stand;a++)
			for(int b=0;b<=stand;b++)
				for(int c=0;c<=stand;c++){
					if(check(a,b,c)){
						ans+=f[i][a][b][c];
					}
					if(nex[i][0]<=n) f[nex[i][0]][a+1][b][c]=(f[nex[i][0]][a+1][b][c]+f[i][a][b][c])%mod;
					if(nex[i][1]<=n) f[nex[i][1]][a][b+1][c]=(f[nex[i][1]][a][b+1][c]+f[i][a][b][c])%mod;
					if(nex[i][2]<=n) f[nex[i][2]][a][b][c+1]=(f[nex[i][2]][a][b][c+1]+f[i][a][b][c])%mod;
				}
	cout<<ans%mod<<endl;
        return 0;
 }

T3 CF19E

如果熟悉二分图和各种树上操作的话应该不难(但是我不会)。

思路很明确:给一张无向图,求删掉哪几条边可以变成二分图;

定义一个环为奇环当且仅当环包含的边数为奇数,偶环同理。

我们会发现,

    如果没有奇环,那么任何边都可以删;

    如果只有一个奇环,那么奇环上的边都可以被删;

    如果多于1个,必须选在所有奇环且不在偶环上的点;

那么怎么找奇环呢?

这里要说到DFS树的两个特点,第一,树上的边要么构成一条链,要么是返祖边。第二,树上的LCA就是深度较浅的点。

这里的返祖边就是我们要找的环了。考虑到树上差分,它应该在两个端点+1,LCA-2。根据上面的第二点,我们将深度较浅的点+1-2即可。

统计tag==n的点,输出他的father即可。


#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define ri register int
#define il inline
#define fi first
#define se second
#define mp make_pair
#define pi pair<int,int>
#define mem0(x) memset((x),0,sizeof (x))
#define mem1(x) memset((x),0x3f,sizeof (x))
#define gc getchar
#define pb push_back
template<class T>void in(T &x){
    x = 0; bool f = 0; char c = gc();
    while (c < '0' || c > '9') {if (c == '-') f = 1; c = gc();}
    while ('0' <= c && c <= '9') {x = (x << 3) + (x << 1) + (c ^ 48); c = gc();}
    if (f) x = -x;
}
#undef gc
void out(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) out(x / 10);
    putchar(x % 10 + '0');
}
#define N 10010
#define M N<<2
int n, m;
int v[M], u[M], nx[M], k[M];
int cnt = 1, head[N];
il void add(int uu, int vv) {
    u[++cnt] = uu, v[cnt] = vv, nx[cnt] = head[uu];
    head[uu] = cnt;
    k[cnt] = (cnt >> 1);
}
int dep[N];
bool vis[N];
int tag[N];
int zt;
vector<int>ans;
int lst;
void dfs(int x, int f, int d) {
    //printf("V %d\n",x);
    dep[x] = d;
    vis[x] = 1;
    for (ri i = head[x]; i; i = nx[i]) {
        if (i == f) continue;
        if (!vis[v[i]]) {
            dfs(v[i], i ^ 1, d + 1);
        }
        else {
            if (dep[v[i]] < dep[x]) continue;
            if ((dep[v[i]] - dep[x] + 1) & 1) {
                tag[x]--;  // in fact +1 -2
                tag[v[i]]++;
                zt++;
                lst=k[i];
            }
            else {
                tag[x]++;
                tag[v[i]]--;
            }
        }
    }
}
int dfs2(int x, int f) {
    vis[x] = 1;
    int res = tag[x];
    for (ri i = head[x]; i; i = nx[i]) {
        if (vis[v[i]]) continue;
        res += dfs2(v[i], i);
    }
    if (res == zt) ans.pb(k[f]);
    //printf("T %d %d %d\n",x,res,tag[x]);
    return res;
}
signed main() {
    in(n), in(m);
    for (ri i = 1, a, b; i <= m; ++i) {
        in(a), in(b);
        add(a, b);
        add(b, a);
    }
    for (ri i = 1; i <= n; ++i) {
        if (!vis[i]) dfs(i, 0, 0);
    }
    if (zt == 0) {
        //cout << "A";
        printf("%d\n", m);
        for (ri i = 1; i <= m; ++i) printf("%d ", i);
        return 0;
    }
    if(zt==1) ans.pb(lst);
    //cout<<zt;
    mem0(vis);
    for (ri i = 1; i <= n; ++i) {
        if (!vis[i]) dfs2(i, 0);
    }
    sort(ans.begin(), ans.end());
    printf("%d\n", ans.size());
    for (ri i = 0; i < ans.size(); ++i) {
        printf("%d ", ans[i]);
    }
    return 0;
}

NOIp 2015 day2 解题报告

T1 跳石头

二分即可


#include<bits/stdc++.h>
using namespace std;
int n,m,d,l,r,mid,ans,a[100000];
bool judge(int x){
	int tot=0,i=0,now=0;
	while (i<n+1) {
		i++;
		if(a[i]-a[now]<x) tot++;
		else now=i;
		
	}
	 return tot<=m;
}
int main(){
 	freopen("stone.in","r",stdin);
 	freopen("stone.out","w",stdout);	
	cin>>d>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	a[n+1]=d,l=1,r=d;
	while(l<=r){
		mid=(l+r)/2;
		if (judge(mid)){
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	cout<<ans;
	return 0;
}

T2 子串

DP。我觉得和背包的思路挺像的一道题。用f1[i][j][k]表示两串分别到a[i],b[j],且a[i]必须使用时,用了k个子串的方案数。f2[i][j][k]表示无论用不用当前字符(A[i])时的方案数总和。那么转移方程就显而易见了:

f1[i][j][k]=f1[i-1][j-1][k-1]+f2[i-1][j-1][k]

f2[i][j][k]=f2[i][j][k]+f[i][j][k]

我们发现i这个维度是可以滚动的,于是就节省了一维空间


#include<bits/stdc++.h>
using namespace std;
int mod=1000000007;
char a[1001],b[201];
int n,m,k1,y,ans,f1[1001][200],f2[201][201];
int main(){
	//freopen("substring.in","r",stdin);
 	//freopen("substring.out","w",stdout);
	cin>>n>>m>>k1;
	cin>>a>>b;
	f1[0][0]=f2[0][0]=1;
    for (int i=1; i<=n; i++)
        for (int j=m; j>=1; j--)
            for (int k=1; k<=k1; k++)
            {
                if (a[i-1]!=b[j-1]) {f1[j][k]=0; continue;}
                f1[j][k]=(f1[j-1][k]%mod+f2[j-1][k-1]%mod)%mod;
                f2[j][k]=(f2[j][k]%mod+f1[j][k]%mod)%mod;
            }
    printf("%d",f2[m][k1]);
	return 0;
}

T3 运输计划

先Tarjan求m对点的lca,然后二分答案,把所有大于二分的答案的路径记录,判断有没有一条边被所有大于二分的答案的路径所重复,如果有,那么这条边必须大于等于最大的路径减二分的答案,如果上述两个条件都符合那么这个二分的答案就是合法的,细节参考代码。如果TLE加个快读即可。


#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef pair<int,int>PP;
struct node{
    int to,dist,nu;
};
struct P{
    int a,b,c;
};
int q[300002],n,m,hh[300002],ans[300002],anss[300002],lca[300002],h[300002],fa[300002],left,right,mid,f[300002];
P y[300002];
bool fl[300002];
vector<node>s[300002];
vector<PP>t[300002];
int find(int x){
    if (fa[x]==x)return x;
    else return fa[x]=find(fa[x]);
}
void dfs(int x,int tt){
    anss[x]=tt;fa[x]=x;
    for (int i=0;i<s[x].size();i++)
    if (!fa[s[x][i].to])
    {
        h[s[x][i].to]=s[x][i].nu;
        dfs(s[x][i].to,tt+s[x][i].dist);
        fa[s[x][i].to]=find(x);
    }
    for (int i=0;i<t[x].size();i++)
    if (fa[t[x][i].first])lca[t[x][i].second]=find(t[x][i].first);
}
void dfs(int x){//c++支持重载函数
    for (int i=0;i<s[x].size();i++)
    if (!fl[s[x][i].to])
    {
        fl[s[x][i].to]=1;
        dfs(s[x][i].to);
        f[h[x]]+=f[h[s[x][i].to]];
    }
}
bool pd(int x){
    int t=0,u,maxn=0;
    memset(f,0,sizeof(f));
    memset(fl,0,sizeof(fl));
    for (int i=0;i<m;i++)
    if (ans[i]>x){maxn=max(ans[i],maxn);q[t++]=i;}
    if (!t)return true;
    for (int i=0;i<t;i++)
    {
        u=q[i];
        f[h[y[u].a]]++;//这条边+1
        f[h[y[u].b]]++;//这条边+1
        f[h[lca[u]]]-=2;//这条边-2,因为这条边虽然计算了两次,实际上一次都没有走到
    }
    dfs(1);
    for (int i=1;i<n;i++)
    if (f[i]==t && hh[i]>=maxn-x)return true;
    return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    int u,v,w,sum=0;
    for (int i=1;i<n;i++)
    {
        scanf("%d%d%d",&u,&v,&w);sum+=w;
        hh[i]=w;node g;
        g.to=v;g.dist=w;g.nu=i;
        s[u].push_back(g);
        g.to=u;
        s[v].push_back(g);
    }
    for (int i=0;i<m;i++)
    {
        scanf("%d%d",&y[i].a,&y[i].b);
        t[y[i].a].push_back(PP(y[i].b,i));
        t[y[i].b].push_back(PP(y[i].a,i));
    }
    dfs(1,0);
    for (int i=0;i<m;i++)
    ans[i]=anss[y[i].a]+anss[y[i].b]-2*anss[lca[i]];//a到b的距离等于1到a的距离+1到b的距离-1到lca(a,b)的距离
    left=0;right=sum;
    while(left<right)//二分答案
    {
        mid=(left+right)/2;
        if (pd(mid))right=mid;
        else left=mid+1;
    }
    printf("%d",right);
    return 0;
}

以上

NOIp 2015 day1 解题报告

T1 神奇的幻方

按照题意模拟即可。

T2 信息传递

也是很水的一道题。有两种方法可以做。第一种是tarjan来求最小环,这个代码量略大。另外一种就是利用改版并查集,father数组存的是最远的祖先。具体实现看代码


#include<bits/stdc++.h>
using namespace std;
int f[200010],d[200010],n,minn,last;
int find(int x){
	if (f[x]!=x){
		int tmp=f[x];
		f[x]=find(f[x]);
		d[x]+=d[tmp];
	}
	return f[x];
}
int main(){
	freopen("message.in","r",stdin);
 	freopen("message.out","w",stdout);
	cin>>n;
	for (int i=1;i<=n;i++) f[i]=i;
	minn=0x7f7f7f7f;
	for (int i=1;i<=n;i++) {
		int t;
		cin>>t;
		int x=find(i),y=find(t);
		if (x!=y) f[x]=y,d[i]=d[t]+1;
		  else minn=min(minn,d[i]+d[t]+1);
	}
	cout<<minn;
	return 0;
}

T3 斗地主

这题挺有意思的,写完后发现会了很多斗地主技巧。首先一个显而易见的事实:有顺子时打顺子不一定是最优的,比如说我有一副 3 4 5 6 6 7 7 8 9 10,如果要打最长的顺子,这套牌要分三次才能打完。于是乎,我们只能暴力枚举了。另外其实还需要考虑其他的细节,但是这一题是随机数据,所以不加这些细节也能AC。如果要更高端的解法请移步这一题。


#include<bits/stdc++.h>
using namespace std;
int t,n,a[20],ans;
void dfs(int x){
	if (x>ans) return;
int s1,s2,s3,s4;
    s1=s2=s3=s4=0;
    for(int i=1;i<=14;i++)//统计单牌与对子
    {
     if(a[i]==1) s1++;
     if(a[i]==2) s2++;
    }
    for(int i=1;i<=14;i++)//四带的牌
    if(a[i]==4) 
    { 
     s4++;
     if(s1>=2) s1-=2;
     else if(s2>=2) s2-=2;
     else if(s2>=1) s2--;
    }
    for(int i=1;i<=14;i++)//三带的牌
    if(a[i]==3)
    {
        s3++;    
        if(s1>=1) s1--;
        else if(s2>=1) s2--;
    }
    ans=min(ans,x+s1+s2+s3+s4);//手中没牌,更新方案
    
	//以下枚举顺子 
	int j;
	for(int i=1;i<=8;i++){
		for (j=i;j<=12;j++){
			a[j]--;
			if (a[j]<0) break;
			if(j-i>=4) dfs(x+1);
		}
		if (j>=13) j--;
		while (j>=i) a[j--]++;
	}
	for (int i=1;i<=10;i++){
		for(j=i;i<=12;j++){
			a[j]-=2;
			if(a[j]<0) break;
			if(j-i>=2) dfs(x+1);
		}
		if (j>=13) j--;
		while (j>=i) a[j--]+=2;
	}
	for (int i=1;i<=11;i++){
		for(j=i;i<=12;j++){
			a[j]-=3;
			if(a[j]<0) break;
			if(j-i>=1) dfs(x+1);
		}
		if (j>=13) j--;
		while (j>=i) a[j--]+=3;
	}
	
}

int main(){
	freopen("landlords.in","r",stdin);
 	freopen("landlords.out","w",stdout);
	cin>>t>>n;
	for(int i=1;i<=t;i++){
		memset(a,0,sizeof(a));
		for(int i=1;i<=n;i++){
			int x,y;
			cin>>x>>y;
			if (x==0) a[14]++;
			if (x==1) a[12]++;
			if (x==2) a[13]++;
			if (x>=3) a[x-2]++;
		}
		ans=0x7f7f7f;
		dfs(0);
		cout<<ans<<endl;
	}
	return 0;
} 

以上

2018.7.19 题解

日狗了没有自动保存

题目找不到留言,我私发

T1 城市轰炸

题目大意

给出一张有向图,可能有环,求最长链

思路

求最长链我们可以用拓扑排序。但是拓扑排序不能有环,怎么处理呢?其实环就是一个强联通分量,所以我们可以用tarjan把强联通分量求出来,并且将其合并成一个点。这样问题就解决了。

代码


#include<cstdio>
using namespace std;
const int N=1e6+1;
int tot,top,num,ans;
int first[N],next[N],en[N];
int first1[N],next1[N],en1[N];
int dfn[N],low[N],stack[N],MS[N],r[N];
int f[N],g[N],d[N],q[N],bel[N];
bool bz[N],vis[N];
inline int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
inline int max(int x,int y)
{
    return x>y?x:y;
}
inline int min(int x,int y)
{
    return x<y?x:y;
}
inline void insert(int x,int y)
{
    next[++tot]=first[x];
    first[x]=tot;
    en[tot]=y;
}
inline void insert1(int x,int y)
{
    next1[++tot]=first1[x];
    first1[x]=tot;
    en1[tot]=y;
    d[y]++;
}
inline void tarjan(int x)
{
    MS[MS[0]=1]=x;
    while(MS[0])
    {
        if(!dfn[x=MS[MS[0]]])
        {
            dfn[x]=low[x]=++tot;
            r[stack[++top]=x]=first[x];
            bz[x]=vis[x]=true;
        }
        bool enter=false;
        for(int i=r[x];i;i=next[i])
            if(!vis[en[i]])
            {
                r[x]=next[i];
                MS[++MS[0]]=en[i];
                enter=true;
                break;
            }else
                if(bz[en[i]]) low[x]=min(low[x],dfn[en[i]]);
        if(enter) continue;
        if(dfn[x]==low[x])
        {
            num++;
            do
            {
                bel[stack[top]]=num;
                bz[stack[top--]]=false;
            }while(stack[top+1]!=x);
        }
        low[MS[--MS[0]]]=min(low[MS[MS[0]]],low[x]);
    }
}
int main()
{
    freopen("bomb.in","r",stdin);
    freopen("bomb.out","w",stdout);
	int n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        insert(x,y);
    }
    tot=0;
    for(int i=1;i<=n;i++)
        if(!vis[i]) tarjan(i);
    tot=0;
    for(int i=1;i<=n;i++)
    {
        g[bel[i]]++;
        for(int j=first[i];j;j=next[j])
            if(bel[i]^bel[en[j]]) insert1(bel[i],bel[en[j]]);
    }
    int l=0,r=0;
    for(int i=1;i<=n;i++)
        if(!d[i]) f[q[++r]=i]=g[i];
    while(l<r)
    {
        int x=q[++l];
        if(!first1[x]) ans=max(ans,f[x]);
        for(int i=first1[x];i;i=next1[i])
        {
            f[en1[i]]=max(f[en1[i]],f[x]+g[en1[i]]);
            if(!--d[en1[i]]) q[++r]=en1[i];
        }
    }
    printf("%d\n",ans);
    return 0;
}

T2 密室

题目大意

有一坨房间,房间里可能有门或者钥匙。我们初始在房间x,要走到房间y。门通向另一个房间,但需要特定的数把钥匙才能开。求x到y的最短路径。

思路

看上去没思路,于是就写个bfs,发现bfs可以过。

代码


#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
struct node
{
	int dot,s,step;
	node(){}
	node(int x,int y,int z):dot(x),s(y),step(z){}
};
struct node1
{
	int to,s;
	node1(){}
	node1(int x,int y):to(x),s(y){}
};
int n,m,k,a[5005];
bool b[5005][2048];
queue<node> p;
vector<node1> c[5005];
void bfs()
{
	p.push(node(1,a[1],0));
	while (!p.empty())
	{
		node u=p.front();
		p.pop();
		if (u.dot==n)
		{
			cout<<u.step;
			return;
		}
		for (int i=0;i<c[u.dot].size();i++)
		{
			int v=c[u.dot][i].to;
			if ((u.s&c[u.dot][i].s)==c[u.dot][i].s&&b[c[u.dot][i].to][u.s|a[v]]==0) 
			{
				b[c[u.dot][i].to][u.s|a[v]]=1;
				p.push(node(v,u.s|a[v],u.step+1));
			}
		}
	}
}
int main()
{
	freopen("room.in","r",stdin);
	freopen("room.out","w",stdout);
	cin>>n>>m>>k;
	int x,y,z,t;
	for (int i=1;i<=n;i++) 
	{
		for (int j=1;j<=k;j++)
		{
			cin>>x;
			a[i]=a[i]*2+x;
		}
	}
	for (int i=1;i<=m;i++)
	{
		cin>>y>>z;
		t=0;
		for (int j=1;j<=k;j++)
		{
			cin>>x;
			t=t*2+x;
		}
		c[y].push_back(node1(z,t));
	}
	bfs();
}

T3 序列

思路

莫队算法,专门开一篇填坑

HDU3400 三分查找

题目大意

HDU3400.在二维平面上有两条线段AB、CD。AB上的速度是P,CD上是Q,平面上的其他区域上速度为R。求A->D的最少时间。


思路

由于我们必须在A出发,在D结束,所以我们必然会在两条线段上行走一段距离。而知道在这两条线段上分别应当走多少距离,就是这道题的关键。我们设点q,p分别是在AB CD上的两个点,暴力枚举这两个点的位置(即距离A、D的距离)显然是不行的。而面对求一个极值(在这题里是最短时间)的问题二分也不适用,于是我们就有了三分查找。


三分简介

三分查找与二分相似。从名字就可以看出,三分的每次操作就是用两个点把当前的区间分成三个区间。即在二分查找的基础上,在右区间(或左区间)再进行一次二分。该算法通常用来迅速确定最值。二分查找所面向的搜索序列的要求是:具有单调性。而三分查找所面向的搜索序列的要求是:序列为一个凸性函数。即该序列必须有一个最大值(或最小值),在极值的左侧序列,必须满足不严格单调递增(递减),右侧序列必须满足不严格单调递减(递增)。如下图,表示一个有最大值的凸性函数:

    

算法流程

(1)与二分法类似,先取整个区间的中间值mid。

 

(2)再取右侧区间的中间值midmid,从而把区间分为三个小区间。

(3)如果mid比midmid更靠近最值,我们就舍弃右区间,否则我们舍弃左区间。

比较mid与midmid谁最靠近最值,只需要确定mid所在的函数值与midmid所在的函数值的大小。当最值为最大值时,mid与midmid中较大的那个自然更为靠近最值。最值为最小值时同理。

(4)重复(1)(2)(3)直至找到最值。

代码

挖个坑以后再填(逃)

总结

因为大多数题不用单独写judge函数,所以总体上看三分比二分更水。但是每个算法都有它的必要性,三分也是如此。(众:这就是你又水了一贴的理由?)

NOIp 2014 Day2 题解

T1 无线网络发射器选址

先看数据范围,128*128?果断直接暴力模拟发射器的位置,但是在计算在(x-d,y-d)到(x+d,y+d)中的之和时要注意边界和范围。什么?代码呢?这么斯波的题自己去写。

T2 联合权值

这道题记得很早以前就做过。条件1如何解决是这道题的难点。如果直接对每个点进行判断能否抵达终点显然会超时的。那么既然我们要判断哪些点能到终点,为啥不从终点反着广搜一遍,看看终点能到哪些点呢?于是思路就显而易见了。将所有路径反向,从终点开始做一遍BFS标记出哪些点是可以作为合法路径,再做一遍spfa(其实貌似BFS也能过)就搞定了。


#include<bits/stdc++.h>
#define N 200005
#define inf 0x3f3f3f3f
using std::queue;

struct Edge{
    int v,nxt;
}e[N],E[N];

int n,m,s,t;
int head[N],tot;
int h[N],tol;

void AddEdge(int u,int v){
    e[++tot]=(Edge){v,head[u]};head[u]=tot;//正向边
    E[++tol]=(Edge){u,h[v]};h[v]=tol;//反向边
}

int vis[N];//表示这个点能直接到达终点
int be[N];//if be[i]== true 表示i没有被删除
int tt;

int dfs(int x,int f){//反向遍历
    vis[x]=true;
    for(int i=h[x];i;i=E[i].nxt){//反向边
        int to=E[i].v;
        if(to==f)continue;
        if(vis[to])continue;
        dfs(to,x);
    }
}

int init(){//删除不能存在于路径上的点
    for(int i=1;i<=n;++i){
        if(!vis[i])continue;bool flag=0;
        for(int j=head[i];j;j=e[j].nxt){
            int to=e[j].v;
            if(!vis[to])flag=true;
        }
        if(!flag)be[i]=true;
    }
}

int dis[N];

int spfa(int s,int t){// 从起点到终点的最短路
    memset(dis,0x3f,sizeof(dis));
    memset(vis,false,sizeof(vis));
    int top,to;
    dis[s]=0;queue<int>que;que.push(s);vis[s]=true;
    while(!que.empty()){
        top=que.front();
        que.pop();
        for(int i=head[top];i;i=e[i].nxt){
            to=e[i].v;
            if(!be[to])continue;           
            int l;
            if(dis[top]+1<dis[to]){
                dis[to]=dis[top]+1;
                if(!vis[to]){
                    vis[to]=true;
                    que.push(to);
                }
            }
        }
    }
    return dis[t];
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        AddEdge(u,v);
    }
    scanf("%d%d",&s,&t);
    dfs(t,0);
    init();
    int ans=spfa(s,t);
    if(ans==inf)ans=-1;
    printf("%d\n",ans);
    return 0;
}

T3 解方程

狗比数学题。如果我们有个一元四次方程a0+a1x+a2x^2+a3x^4+a4x^4=0,那么其显然等于(x(x(xa4+a3)+a2)+a1)+a0=0。这就是一个叫做秦九韶的人提出的imba解法(这货是宋朝人!)。换句话讲,对于一个多项式,维护一个初始值为0的sum,每一次先加上当前的系数a,再自乘x,这个多项式如果和为0,则这是方程的一个解。但是这个a比较大(dark),高精度又容易被卡常,于是我们就可以先给a取模。这里我要介绍一个非常excited的素数:19260817,据信这个素数可以极大的优化程序的运行时间,通常为1s。


#include<bits/stdc++.h>
#define maxn 105
#define MOD1 23333
#define MOD2 19260817
using namespace std;
int n,m,a1[maxn],a2[maxn],vis[1000005];
vector<int>ans;
void read(int pos){
    char c=getchar();
    int x1=0,x2=0,f=1;
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x1=(x1*10+c-'0')%MOD1;
        x2=(x2*10+c-'0')%MOD2;
        c=getchar();
    }
    a1[pos]=x1*f;
    a2[pos]=x2*f;
}
int check(int f[],int x,int mod) {
            int y=0;
            for(int i=n;i>=0;i--)
            y=((long long)y*x+f[i])%mod;                       
            return (!y)? 1:0;
}
int main(){
    freopen("equation.in","r",stdin);
    freopen("equation.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++)read(i);
    for(int i=1;i<=MOD1;i++)
       if(check(a1,i,MOD1))
           for(int j=i;j<=m;j+=MOD1)
             if(check(a2,j,MOD2))
                    vis[j]=1;
    for(int i=1;i<=m;i++)
         if(vis[i])ans.push_back(i);
    printf("%d\n",ans.size());
    for(int i=0;i<ans.size();i++)printf("%d\n",ans[i]);
    return 0;
}

以上

NOIp 2014 Day1 题解

T1 生活大爆炸版石头剪刀布

一看就是一道水题。根据得分关系列一张二维表,然后按题意循环就OK了。代码没必要贴。

T2 联合权值

不难看出这是一颗无根树。因为没有环,所以如果两个点同时于另一个点相连,则它们不可能再有边相连,即它们之间的距离为2。于是我们可以枚举每一个点,取其任意两个点,然后进行组合,然后两两相乘,得到最大值与他们的和。将所有值统计一下,输出的时候答案不要忘记乘二。
代码如下:


#include<bits/stdc++.h>
using namespace std;
int head[200010],ll,v[200010];
struct st{int to,nxt;}a[400010];
int n,ans,x,y,maxans;
void add(int x,int y){
	a[++ll].to=y,a[ll].nxt=head[x],head[x]=ll;
}
int main(){
	freopen("link.in","r",stdin);
 	freopen("link.out","w",stdout);
	cin>>n;
	for (int i=1;i<n;i++){
		cin>>x>>y;
		add(x,y); add(y,x);
	}
	for (int i=1;i<=n;i++) cin>>v[i];
	for (int i=1;i<=n;i++){
		int sum=0,ma=0,m=0;
		for (int k=head[i];k>0;k=a[k].nxt){
			if (v[a[k].to]>ma) m=ma,ma=v[a[k].to];
			else if (v[a[k].to]>m) m=v[a[k].to];
			ans=(ans+sum*v[a[k].to]) % 10007;
			sum=(sum+v[a[k].to]) % 10007;
		}
		maxans=max(maxans,ma*m);
	}
	cout<<maxans<<" "<<(ans*2)%10007;
}

T3 飞扬的小鸟

为了理解这道题,我特地去下了一个Flappy Bird。真·休闲游戏(摔)。废话不多说,我们来看题。这道题明眼人一看就知道是道DP,而且因为每个时刻可以按很多下,你们想到了什么算法?对,就是完全背包。注意下落的时候还要搞一遍01背包。说说不难,其实小细节非常多。比如场景的高度有限制,有些优化其实会WA等等,就不一一列举了,请自行体会XD

代码如下:


#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10005,maxm=1005;
int n,m,p;
int clic[maxn],uncl[maxn],L[maxn],H[maxn];
int f[maxn][maxm],cnt,INF;
inline int read(){
    int x=0; char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
int main(){
    freopen("bird.in","r",stdin);
    freopen("bird.out","w",stdout);
    n=read(),m=read(),p=read();
    for (int i=1; i<=n; i++) clic[i]=read(),uncl[i]=read();
    for (int i=1; i<=n; i++) L[i]=0,H[i]=m+1;
    for (int i=1; i<=p; i++){
        int x=read(); L[x]=read(),H[x]=read();
    }
    memset(f,63,sizeof f),INF=f[0][0],cnt=0;
    for (int i=0; i<=m; i++) f[0][i]=0;
    for (int i=1; i<=n; i++){
        for (int j=1; j<H[i]; j++) if (j>=clic[i]&&j!=m)
        f[i][j]=min(f[i][j],min(f[i-1][j-clic[i]]+1,f[i][j-clic[i]]+1));
        for (int j=1; j<H[i]; j++) if (j+uncl[i]<=m&&j>=L[i]+1)
        f[i][j]=min(f[i][j],f[i-1][j+uncl[i]]);
        if (H[i]==m+1)
            for (int j=m; j>=max(1,m-clic[i]); j--) f[i][m]=min(f[i][m],min(f[i-1][j]+1,f[i][j]+1));
        bool flythr=0;
        for (int j=L[i]+1; j<H[i]; j++) if (f[i][j]<INF){flythr=1; break;}
        for (int j=0; j<=L[i]; j++) f[i][j]=INF;
        if (!flythr){printf("0\n%d",cnt); return 0;}
        if (L[i]>0||H[i]<m) cnt++;
    }
    int ans=1<<30;
    for (int i=1; i<=m; i++) ans=min(ans,f[n][i]);
    printf("1\n%d",ans);
    return 0;
}


以上。