• UOJ#41. 【清华集训2014】矩阵变换 构造


    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ41.html

    题解

    首先写个乱搞:

    一开始每一行都选择第一个非0元素,然后,我们对这个方案不断做更新,直到任意两行选择的值不同。更新方法:如果有两行选了相同的值,那么让靠前的那行选择后一个非0的值。

    交上去。

    过了。

    wtf?

    然后发现证明这个结论我花的时间远远大于AC这题QAQ

    现在我们来证明一下:

    首先,如果这个算法算出解了,那么肯定合法。这个比较显然就不证明了。

    然后,我们来分两步证明一定有解。

    接下来我们称让某一行找下一个非0元素这个操作为“弹出这一行的第一个元素”。

    引理

      假设当前状态下,我们在所有行选择的元素构成的集合为 S ;设若干次更新更新后的集合为 S' ,那么一定有: $Ssubseteq S'$ 。

      证明:每次至少有两个相同我们才让其中一个更新,所以这两行原先的值会被保留。所以满足这个引理。

    接下来我们证明一个命题。

    命题

      在得到答案之前,1~n 中至少有一种数没有被作为某一行的第一个元素“弹出”过。

      证明:如果任意两行选择的元素都不同,那么就得到方案了。在集合 S 中元素种类变成 n 的时刻,我们就得到了方案。设最后一次加入集合 S 的元素是 x ,那么在整个过程中, x 一定没有作为某一行的第一个元素被弹出过,所以命题得证。

    因为在得到答案之前,1~n 中至少有一种数没有被作为某一行的第一个元素“弹出”过,而每一个元素在每一行都会出现一次。考虑那个没有被弹出过的元素,它保证了每一行都不会被弹光,所以一定有解,而且通过这个构造方法可以得到解。

    代码

    #pragma GCC optimize("Ofast","inline")
    #include <bits/stdc++.h>
    #define clr(x) memset(x,0,sizeof (x))
    #define For(i,a,b) for (int i=a;i<=b;i++)
    #define Fod(i,b,a) for (int i=b;i>=a;i--)
    #define pb push_back
    #define mp make_pair
    #define fi first
    #define se second
    #define _SEED_ ('C'+'L'+'Y'+'A'+'K'+'I'+'O'+'I')
    #define outval(x) printf(#x" = %d
    ",x)
    #define outvec(x) printf("vec "#x" = ");for (auto _v : x)printf("%d ",_v);puts("")
    #define outtag(x) puts("----------"#x"----------")
    #define outarr(a,L,R) printf(#a"[%d...%d] = ",L,R);
    						For(_v2,L,R)printf("%d ",a[_v2]);puts("");
    using namespace std;
    typedef long long LL;
    LL read(){
    	LL x=0,f=0;
    	char ch=getchar();
    	while (!isdigit(ch))
    		f|=ch=='-',ch=getchar();
    	while (isdigit(ch))
    		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    	return f?-x:x;
    }
    const int N=405;
    int T,n,m;
    int a[N][N];
    int p[N],id[N],cnt[N];
    bool cmp(int a,int b){
    	return p[a]<p[b];
    }
    int Getnxt(int i,int &p){
    	while (!a[i][p]&&p<=m)
    		p++;
    	return p<=m;
    }
    void solve(){
    	n=read(),m=read();
    	clr(a),clr(p);
    	For(i,1,n)
    		For(j,1,m)
    			a[i][j]=read();
    	For(i,1,n)
    		Getnxt(i,p[i]=1);
    	while (1){
    		clr(cnt);
    		int flag=0;
    		For(i,1,n){
    			cnt[a[i][p[i]]]++,id[i]=i;
    			if (cnt[a[i][p[i]]]>1)
    				flag=1;
    		}
    		if (!flag)
    			break;
    		sort(id+1,id+n+1,cmp);
    		For(i,1,n)
    			if (cnt[a[id[i]][p[id[i]]]]>1){
    				if (!Getnxt(id[i],++p[id[i]])){
    					puts("(^o^)/");
    					return;
    				}
    				break;
    			}
    	}
    	For(i,1,n)
    		printf("%d ",a[i][p[i]]);
    	puts("");
    }
    int main(){
    	T=read();
    	while (T--)
    		solve();
    	return 0;
    }
    

      

  • 相关阅读:
    动态规划 最长公共子序列 LCS,最长单独递增子序列,最长公共子串
    梳排序(Comb sort)
    地精排序(Gnome Sort) 算法
    vs2010 调试 调用堆栈 窗口
    vs2010 条件断点 has changed是什么意思?
    vs2010根据字符串内容添加断点
    vs2010 调试中监视变量
    vs2010断点使用技巧
    区间重合判断(pojg校门外的树)
    转:Linus:利用二级指针删除单向链表
  • 原文地址:https://www.cnblogs.com/zhouzhendong/p/UOJ41.html
Copyright © 2020-2023  润新知