Topcoder SRM 600 div1题解
日常TC计划正式启动!
Easy(250pts):
题目大意:给你一个集合,里面一堆数,初始数为0,给你一个目标数,你可以选择集合中若干个数进行OR操作来得到目标数。问至少删去多少个数,使得你永远无法达成目标,保证集合最多50个数,每个数<=10^9,且集合中的数两两互不相同。
显然我们要按照每个数的二进制位来考虑,由于所有的数<=10^9,所以最多只有30个二进制位。
如果集合中的一个数,存在某一位使得这个数为1,但是目标数为0,那么这个数一定不能取。
(因为如果取了,那么当前的数这一位就变成了1,然而不存在从1变成0的操作,所以永远无法成为目标数)
对于剩下的数,如果不能达到目标数,那么一定是存在一个二进制位使得目标数为1,其它数都是0。
那么显然只要扫一下就可以了。
时间复杂度O(n*logw),代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 class ORSolitaire 4 { 5 public: 6 int getMinimum(vector <int> numbers,int t) 7 { 8 int ans=50; 9 int n=numbers.size(); 10 bool flag[57]; 11 for (int i=0;i<n;i++) 12 { 13 flag[i]=true; 14 for (int j=0;j<=30;j++) 15 if ((numbers[i]&(1<<j))&&((t&(1<<j))==0)) flag[i]=false; 16 } 17 for (int i=0;i<=30;i++) 18 { 19 int cnt=0; 20 for (int j=0;j<n;j++) 21 if ((numbers[j]&(1<<i))&&flag[j]) ++cnt; 22 if (t&(1<<i)) ans=min(ans,cnt); 23 } 24 return ans; 25 } 26 };
Medium(600pts):
题目大意:给你一个n*m的01矩阵,你需要尽可能少地改变矩阵中的数(0变成1,1变成0)使得矩阵中至少有r行是回文串,c列是回文串,输出最少操作次数。其中2<=n,m<=14,数据保证n和m都是偶数。
这应该是这场最烦的题目了吧,如果你会玄学优化,说不定可以搜过去~~~(大雾)
我们枚举2^m种的情况,每一列有回文或者不回文的情况,接下来我们考虑使用dp。
考虑dp的状态,如果只是一列一列的进行状态的转移,那么这样显然是不太好做的,因为你不知道这一行是否回文。
于是我们考虑首尾两行,两行两行进行转移。
我们可以先预处理出cost数组,cost[i][j]表示第i行和第n-1-i行一共有j个串是回文串的最小代价。(所以j=0,1,2)
这里我们显然可以通过枚举出2*2的小矩阵,对于2*2我们只要手判就可以了。
接下来考虑转移dp数组,f[i][j]表示前i行和后i行一共有j个串是回文串的最小代价。
于是我们有f[i][j]=min(f[i][j],f[i-1][j-k]+cost[i][k]),中间一些细节(特别是下标)考虑清楚就好了。
时间复杂度O(2^n*n^2),代码如下:
1 #include <bits/stdc++.h> 2 #define inf 20000007 3 using namespace std; 4 int con[5][5],a[57][57]; 5 int n,m; 6 class PalindromeMatrix 7 { 8 int countbit(int s) 9 { 10 int st=s,ans=0; 11 while (st>0) 12 { 13 ans+=(st%2==1); 14 st/=2; 15 } 16 return ans; 17 } 18 int deal(int p0,int p1,int q0,int q1) 19 { 20 if (p0) p0=1; 21 if (p1) p1=1; 22 if (q0) q0=1; 23 if (q1) q1=1; 24 //p means whether two blocks in the row are the same 25 //q means whether two blocks in the column are the same 26 //the aim of the function is just to calculate the possible answer of the blocks of size 2*2 27 int cnt=0; 28 for (int i=0;i<=1;i++) 29 for (int j=0;j<=1;j++) 30 cnt+=(con[i][j]==1); 31 if (p0+p1+q0+q1>=3) return min(cnt,4-cnt); 32 //all the four blocks must be the same if the sum of p&q are not less than 3 33 else if(p0+p1+q0+q1==2) 34 { 35 //there must be exact three blocks that are the same or two blocks in the two rows/columns 36 //then there will be four conditions 37 if (p0&&p1) return ((con[0][0]!=con[0][1])+(con[1][0]!=con[1][1])); 38 if (q0&&q1) return ((con[0][0]!=con[1][0])+(con[0][1]!=con[1][1])); 39 int tot=0; 40 if (p0&&q0) tot=cnt-(con[1][1]==1); 41 if (p0&&q1) tot=cnt-(con[1][0]==1); 42 if (p1&&q0) tot=cnt-(con[0][1]==1); 43 if (p1&&q1) tot=cnt-(con[0][0]==1); 44 return min(tot,3-tot); 45 } else if (p0+p1+q0+q1==1) 46 { 47 //there will also be four conditions 48 int tot=0; 49 if (p0) tot+=(con[0][0]!=con[0][1]); 50 if (p1) tot+=(con[1][0]!=con[1][1]); 51 if (q0) tot+=(con[0][0]!=con[1][0]); 52 if (q1) tot+=(con[0][1]!=con[1][1]); 53 return tot; 54 } 55 //then the answer must be 0 56 else return 0; 57 } 58 int calc(int rowcnt,int st) 59 { 60 int cost[57][5],f[57][57]; 61 for (int i=0;i<n/2;i++) 62 { 63 for (int j=0;j<=2;j++) cost[i][j]=2*m; 64 //x means whether two blocks in the same row are the same 65 //y means whether two blocks in the same column are the same 66 for (int x1=0;x1<=1;x1++) 67 for (int x2=0;x2<=1;x2++) 68 { 69 int now=0; 70 for (int j=0;j<m/2;j++) 71 { 72 int y1=st&(1<<j),y2=st&(1<<(m-j-1)); 73 con[0][0]=a[i][j]; 74 con[0][1]=a[i][m-j-1]; 75 con[1][0]=a[n-i-1][j]; 76 con[1][1]=a[n-i-1][m-j-1]; 77 now+=deal(x1,x2,y1,y2); 78 } 79 cost[i][x1+x2]=min(cost[i][x1+x2],now); 80 } 81 } 82 for (int i=0;i<=n/2;i++) 83 for (int j=0;j<=rowcnt;j++) 84 f[i][j]=inf; 85 f[0][0]=0; 86 for (int i=1;i<=n/2;i++) 87 for (int j=0;j<=rowcnt;j++) 88 for (int k=0;k<=2&&k<=j;k++) 89 f[i][j]=min(f[i][j],f[i-1][j-k]+cost[n/2-i][k]); 90 return f[n/2][rowcnt]; 91 } 92 public: 93 int minChange(vector<string> s,int rowcnt,int colcnt) 94 { 95 n=s.size(),m=s[0].length(); 96 for (int i=0;i<n;i++) 97 for (int j=0;j<m;j++) 98 a[i][j]=s[i][j]-'0'; 99 int ans=inf; 100 for (int st=0;st<(1<<m);st++) 101 if (countbit(st)==colcnt) ans=min(ans,calc(rowcnt,st)); 102 return ans; 103 } 104 };
Hard(900pts):
题目大意:给你两个整数A,B,求直线y=ax+b将平面分成了多少个部分,(a=0,1...A-1,b=0,1,...B-1)其中1<=A,B<=1200。
这是个很棒棒的公式题。
我们考虑把所有的直线一条一条加进来,那么我们只需要统计出每加进来一条直线会多出多少个平面就可以了。
对于直线y=ax+b,显然它不会和y=ax+c,其中c不等于b的直线相交。
于是我们只需要考虑直线y=ax+b和y=cx+d其中c<a且d<B的直线相交。
我们还需要注意到一个细节就是,如果有三条直线交在了同一个点上,那么对于答案的贡献度会减少1,
换句话说,我们要考虑的本质不是有多少直线相交,而是它们会相交出多少个交点。
考虑直线y=ax+b和y=cx+d,它们的交点P,那么Xp=(d-b)/(a-c),
我们将Xp视为p/q,那么我们只需要考虑本质不同的p/q有多少就可以了,
我们来观察一下p和q的范围,
其中p=d-b,那么p在-b~B-b的范围内,而q=a-c,于是q在1~a的范围内。
那么本质不同的p/q是什么呢,也就是p/q的数值不同,那么只需要考虑p和q互质的情况就可以了。
这里可以用莫比乌斯反演呢,但是A,B<=1200,所以直接暴力就好了。
所以这一题我们先预处理出1~i,1~j中有多少对数互质,接下来直接统计答案就可以了。
时间复杂度O(A^2),代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 long long f[1507][1507]; 4 class LotsOfLines 5 { 6 int gcd(int a,int b) 7 { 8 if (b==0) return a; 9 return (gcd(b,a%b)); 10 } 11 public: 12 long long countDivisions(int a,int b) 13 { 14 memset(f,0,sizeof(f)); 15 for (int i=1;i<=a;i++) 16 for (int j=1;j<=b;j++) 17 f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+(gcd(i,j)==1); 18 long long ans=1+b; 19 for (int i=1;i<a;i++) 20 for (int j=0;j<b;j++) 21 ans=ans+2+f[i][j]+f[i][b-j-1]; 22 return ans; 23 } 24 };
完结撒花~