这场比赛赛后反应坑点很多,比如数据太弱,比如各种坑。。。。然而对自己的练习价值还是很大的

首先上个链接:2015四川省赛4436-4445

百度到的题解:点我看百度题解


先分析每个题,再来分析比赛

A:水题:每个开根号判断就好了

int main(){
	//input;
	while(scanf("%d",&n)!=EOF){
		int flag=1;
		for(int i=1;i<=n;i++){
			scanf("%d",&num);
			if (int(sqrt(num*1.0)*sqrt(num*1.0))!=num) flag=0;
		}
		if (flag) puts("Yes");
		else puts("No");
	}
	return 0;
}

B:题意:定义进位,问n个数,两两相加之后共会有多少个进位总数

脑洞数学题,拿个数举例子的话,68823:

要找尾数3的进位,要找其他数的个位大于等于7的

要找尾数23的进位,要找其他数的个位大于等于77的

要找尾数823的进位,要找其他数的个位大于等于177的

要找尾数8823的进位,要找其他数的个位大于等于1177的

要找尾数68823的进位,要找其他数的个位大于等于31177的


现在的思路已经有了,把每个数的尾数全部截出来,然后跟其他的数的同样长度的尾数进行比较,看看能不能进位。。

但是这样的时间是O(n^2)的

但是,我们用最简单的办法减小复杂度!~二分!


所以,最终的方法为,每个数截取同样长度的数位,各个数位单独从小到大查找自己所需要的“补数”,如823找大于等于177的(但是有个细节,找的时候可能会找到“自己”,待会处理),然后累加即可


上代码:(处理点为  ans  - -  的代码)


vector<ll> G[20];
int x[maxn];

inline void in(int &x){
	char ch;
	x=0;
	ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
}

int main(){
	int n;
	while(scanf("%d",&n)!=EOF){
		for(int i=1;i<=9;i++) G[i].clear();
		for(int i=1;i<=n;i++){
			in(x[i]);
			ll tp=10;
			for(int k=1;k<=9;k++){
				G[k].push_back(x[i]%tp);
				tp*=10;
			}
		}
		for(int i=1;i<=9;i++) sort(G[i].begin(),G[i].end());
		ll ans=0;
		for(int i=1;i<=n;i++){
			ll tp=10;
			for(int k=1;k<=9;k++){
				ll tt=x[i]%tp;
				tt=tp-tt;
				int u=lower_bound(G[k].begin(),G[k].end(),tt)-G[k].begin();
				ans+=G[k].size()-u;
				if (tt<=x[i]%tp) ans--;
				tp*=10;
			}
		}
		printf("%lld\n",ans/2);
	}
	return 0;
}

C题:字符串,给定A和B串,要求在B串中找到A串第一次出现的位置然后删除,将其未删除的左右两部分合并之后继续操作,问最后还剩下哪些字符

首先介绍一个伟大的算法:KMP算法

这个算法的精髓就是Next数组,因为在暴力操作中一旦匹配失败是退回到初始位置继续,而Next数组的处理之后能够退回到“”最有可能"再次匹配的地方(利用之前所有的匹配信息),弄懂了这一点,这个题就好做

然后,很明显,需要kmp_pre处理A串,然后把B串中的匹配字符串删除,未匹配的存在一个新的字符串中,仍然维护B在A中的Next数组

代码如下(特别简洁):其中pos数组仍然代表失去匹配之后下个位置去哪儿找


void kmp_pre(char x[],int m,int Next[]){
	int i=0,j;
	j=Next[0]=-1;
	while(i<m){
		while(j!=-1&&x[i]!=x[j]) j=Next[j];
		Next[++i]=++j;
	}
}

int Next[N],pos[N];
char stk[N];

int KMP_Count(char x[],int m,char y[],int n){
	int i,j,ip;
	kmp_pre(x,m,Next);
	i=j=ip=0;
	while(ip<n){
		stk[i]=y[ip++];
		while(j!=-1&&stk[i]!=x[j]) j=Next[j];
		i++;j++;
		pos[i]=j;
		if (j>=m){
			i-=m;
			j=pos[i];
		}
	}
	return i;
}

char pat[N],src[N];

int main(){
	while(scanf("%s%s",pat,src)==2){
		int l=KMP_Count(pat,strlen(pat),src,strlen(src));
		stk[l]='\0';
		puts(stk);
	}
	return 0;
}

D题:最小点覆盖

弱用二分图匹配水过,,,然而并不能正确的,但是弱弱在这贴个板子

int uN,vN;
int g[maxn][maxn];
int linker[maxn];
bool used[maxn];

bool dfs(int u){
	for(int v=1;v<=vN;v++)
		if (g[u][v]&&!used[v]){
			used[v]=true;
			if (linker[v]==-1||dfs(linker[v])){
				linker[v]=u;
				return true;
			}
		}
	return false;
}

int hungary(){
	int res=0;
	memset(linker,-1,sizeof(linker));
	for(int u=1;u<=uN;u++){
		memset(used,false,sizeof(used));
		if (dfs(u)) res++;
	}
	return res;
}

int main(){
	//input;
	int n,m,u,v;
	while(scanf("%d%d",&n,&m)!=EOF){
		memset(g,0,sizeof(g));
		while(m--){
			scanf("%d%d",&u,&v);
			g[u][v]=g[v][u]=1;
		}
		uN=vN=n;
		printf("%d\n",hungary()/2);
	}
	return 0;
}

在赛后复制了一份巨巨的代码供大家学习暴力的优美姿势:
vector<int> adjs[MAXN];
int last[510];

bool mat[MAXN][MAXN], vis[MAXN];
int n, m;
int res;

bool mustselect(int u) {
    for(int i = 0; i < u; ++i)
        if(!vis[i] && mat[i][u]) return true;
    return false;
}

void dfs(int u, int c) {
    if(c >= res) return ;
    if(u == n) {
        res = c;
    } else {
        vis[u] = true;
        dfs(u + 1, c + 1);
        vis[u] = false;

        if(!mustselect(u)) {
            int t = 0;
            foreach(it, adjs[u]) if(last[*it] == -1) {
                last[*it] = u;
                t++;
            }
            dfs(u + 1, c + t);
            foreach(it, adjs[u]) if(last[*it] == u)
                last[*it] = -1;
        }
    }
}

int solve() {
    memset(vis, 0, sizeof(vis));
    res = n;
    dfs(0, 0);
    return res;
}

int main() {
    while(scanf("%d%d", &n, &m) != EOF) {
        memset(mat, 0, sizeof(mat));
        memset(last, -1, sizeof(last));
        for(int i = 0; i < 30; ++i)
            adjs[i].clear();

        for(int i = 0, a, b; i < m; ++i) {
            scanf("%d%d", &a, &b);
            a--, b--;
            if(a > b) swap(a, b);
            if(b < 30) mat[a][b] = mat[b][a] = true;
            else adjs[a].push_back(b);
        }

        n = min(n, 30);
        printf("%d\n", solve());
    }
    return 0;
}

E题:完美数学题

首先可知,一个任意的长为x宽为y的矩形,如果可以放入n*m的矩形中,那么方案数为(n-x+1)(m-y+1)

现在开始分析:由于对称性,简化细节,保证n大于等于m(如果n<m的话可以swap)

那么,对x和y的限制有三条,x小于等于n,y小于等于m,2(x+y)小于等于k,可知取min()

由于n和m的范围限制,我们可以枚举一维再做考虑,不妨枚举x

则有x的范围为1到min(n,k/2)

那么y的范围是1到min(k/2-x,m)

循环中不能对y循环,但是,发现!!y是从1开始连续的,把公式拆成(n-x+1)(m+1)-y(n-x+1)

y的变化是知道的!所以只需枚举x即可,y得到最大值就可以算出上述的值


int main(){
	//input;
	while(scanf("%lld%lld%lld",&n,&m,&k)!=EOF){
		ans=0;
		if (n<m) swap(n,m);
		for(w=1;w<=min(k/2-1,n);w++){
			l=min(k/2-w,m);
			r=(l+1)*l/2;
			ans+=l*(n-w+1)*(m+1)-(n-w+1)*r;
		}
		cout<<ans<<endl;
	}
	return 0;
}

F题:树状数组维护最大值:也是下载了一份巨巨的代码

思路:枚举10000这个数据点,然后用树状数组维护终点为i的最大的和(左边求一个,右边求一个),再枚举切断点取最大值即可


int lowbit(int x){
	return x&(-x);
}

void modify(int x,int val){
	while(x){
		tree[x]=max(tree[x],val);
		x-=lowbit(x);
	}
}

int query(int x){
	int res=0;
	while(x<=m){
		res=max(res,tree[x]);
		x+=lowbit(x);
	}
	return res;
}

void init(int n){
	memset(tree,0,sizeof(tree));
	lmax[0]=0;
	for(int i=1;i<=n;i++){
		if (a[i]==0||a[i]==m) lmax[i]=lmax[i-1];
		else{
			lmax[i]=a[i]+query(a[i]);
			modify(a[i],lmax[i]);
			lmax[i]=max(lmax[i],lmax[i-1]);
		}
	}
	memset(tree,0,sizeof(tree));
	rmax[n+1]=0;
	for(int i=n;i>0;i--){
		if (a[i]==0||a[i]==m) rmax[i]=rmax[i+1];
		else{
			rmax[i]=a[i]+query(a[i]);
			modify(a[i],rmax[i]);
			rmax[i]=max(rmax[i],rmax[i+1]);
		}
	}
}

int solve(){
	int res=0;
	for(int i=0;i<n;i++)
		if (b[i]==m){
			for(int j=1;j<n;j++) a[j]=b[(i+j)%n];
			init(n-1);
			res=max(res,max(lmax[1],rmax[n-1]));
			for(int i=1;i<n-1;i++)
				res=max(res,lmax[i]+rmax[i+1]);
		}
	return res+m;
}

int main(){
	//input;
	while(scanf("%d",&n)!=EOF){
		for(int i=0;i<n;i++) scanf("%d",&b[i]);
		printf("%d\n",solve());
	}
	return 0;
}

G和H弱弱不会。。。。。。。。。。


I题:坑广搜(暴力还是保平安啊。。。。。)

题意:给定n点完全图,其中m条边长度为a,剩下的所有边长度都为b,求1到n的最短路径

这个题的最大难度不是思路,而是数据量:n最大100000,所以存数据不能用矩阵,跑算法不能O(n^2)

弱比赛的时候没过,赛后巨巨们的思路是用链表或者并查集之类的数据结构维护:当前那些点没有访问过


代码如下:

int head[MAXV], ecnt;
int to[MAXE], nxt[MAXE];
int n, m, a, b;

void init() {
    memset(head + 1, -1, n * sizeof(int));
    ecnt = 0;
}

void add_edge(int u, int v) {
    to[ecnt] = v; nxt[ecnt] = head[u]; head[u] = ecnt++;
    to[ecnt] = u; nxt[ecnt] = head[v]; head[v] = ecnt++;
}

int dis[MAXV];

int solvea() {
    fill(dis + 1, dis + n + 1, b);
    dis[1] = 0;

    queue<int> que; que.push(1);
    while(!que.empty()) {
        int u = que.front(); que.pop();
        for(int p = head[u]; ~p; p = nxt[p]) {
            int v = to[p];
            if(dis[u] + a < dis[v]) {
                dis[v] = dis[u] + a;
                que.push(v);
            }
        }
    }
    return dis[n];
}

int cnt[MAXV];

int solveb() {
    fill(dis + 1, dis + n + 1, a);
    dis[1] = 0;

    priority_queue<pair<int, int> > que;
    memset(cnt + 1, 0, n * sizeof(int));
    for(int p = head[1]; ~p; p = nxt[p])
        cnt[to[p]]++;
    for(int i = 2; i <= n; ++i)
        que.push(make_pair(-cnt[i], i));
    int viscnt = 1, d = b;

    while(!que.empty() && d < a) {
        int upper = viscnt;
        while(!que.empty() && -que.top().first < upper) {
            int c = -que.top().first, u = que.top().second; que.pop();
            if(cnt[u] != c) continue;
            viscnt++;
            dis[u] = d;
            for(int p = head[u]; ~p; p = nxt[p]) {
                int v = to[p];
                if(dis[v] == a && cnt[v] < upper) {
                    cnt[v]++;
                    que.push(make_pair(-cnt[v], v));
                }
            }
        }
        d += b;
        if(viscnt == upper) break;
    }
    return dis[n];
}

int main() {
    while(scanf("%d%d%d%d", &n, &m, &a, &b) != EOF) {
        init();
        for(int i = 0, u, v; i < m; ++i) {
            scanf("%d%d", &u, &v);
            add_edge(u, v);
        }
        if(a < b) printf("%d\n", solvea());
        if(a == b) printf("%d\n", a);
        if(a > b) printf("%d\n", solveb());
    }
}
(虽然比赛的时候各种水的姿势都能过。。。然而,赛后学到方法为好)


J题:暴力枚举题

从(0,0)开始,根据题意,暴力找石头看看会不会撞到,如果会,找一个最近的提前转弯

注意细节点:(1)最大值不够大,在比较点的时候会WA(2)4种方向的最大最小不同,而且求最近

struct NODE
{
	int x,y;
	int tow;
	int vis[4];
}node[2000];

int main(){
	//input;
	int i,j,k,l,t,n,ans;
	while(scanf("%d",&n)!=EOF){
	memset(node,0,sizeof(node));
	for(i=1;i<=n;i++)
		scanf("%d%d",&node[i].x,&node[i].y);
	NODE now;
	now.tow=0;
	now.x=now.y=0;
	ans=0;
	while(1){
		t=0;
		int mini=INF,maxi=-INF;
		if(now.tow==0){
			for(i=1;i<=n;i++){
				if(node[i].y==now.y&&node[i].x>now.x&&mini>node[i].x){
					mini=node[i].x; t=i; 
				}
			}
			if(t!=0) now.x=node[t].x-1;
		}
		if(now.tow==1){
			for(i=1;i<=n;i++){
				if(node[i].x==now.x&&node[i].y<now.y&&maxi<node[i].y){
					maxi=node[i].y; t=i; 
				}
			}
			if(t!=0) now.y=node[t].y+1;
		}
		if(now.tow==2){
			for(i=1;i<=n;i++){
				if(node[i].y==now.y&&node[i].x<now.x&&maxi<node[i].x){
					maxi=node[i].x; t=i; 
				}
			}
			if(t!=0) now.x=node[t].x+1;
		}
		if(now.tow==3){
			for(i=1;i<=n;i++){
				if(node[i].x==now.x&&node[i].y>now.y&&mini>node[i].y){
					mini=node[i].y; t=i; 
				}
			}
			if(t!=0) now.y=node[t].y-1;
		}
		if(t==0){
			cout<<ans<<endl; 
			break;	
		}
		else if(node[t].vis[now.tow]==1){
			cout<<-1<<endl; break;
		}
		else{
			ans++;
			node[t].vis[now.tow]=1;
			now.tow=(now.tow+1)%4;
		}
	}
	}
	return 0;
}

谢谢xdlove巨巨和y761823巨巨的赛后分享代码和思路

————————————————————————————————————————————————————————

今天,中国篮球又让我找到了自己的兴奋点!明天加油!