#include "bits/stdc++.h"
#include <cmath>
using namespace std;

//#define f first
//#define s second

#define pb push_back
#define pp pop_back
#define mp make_pair

#define sz(x) (int)x.size()
#define sqr(x) ((x) * 1ll * (x))
#define all(x) x.begin(), x.end()
#define clr(a,v) memset(a, v, sizeof(a))

#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define per(i, l, r) for (int i = (l); i >= (r); i--)
#define reflex ios_base::sync_with_stdio(false)

#define nl '\n'
#define ioi exit(0);

typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;

typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int N= (int)2e5 + 7;
const int inf = (int)1e9 + 7;
const int mod = (int)1e9 + 7;
//const int mod = 1000000007;
const ll linf = (ll)1e18 + 7;

const int dx[] = {-1, 0, 1, 0, 1, -1, -1, 1};
const int dy[] = {0, 1, 0, -1, 1, -1, 1, -1};
const int kx[] = {-1,1,2,2,1,-1,-2,-2};
const int ky[] = {2,2,1,-1,-2,-2,-1,1};

string conv(int a){stringstream ss;ss << a;string str = ss.str();return str;}
string conv(char a){stringstream ss;ss << a;string str = ss.str();return str;}
int conv(string a){stringstream geek(a); int x = 0; geek >> x;   return x;}

struct node 
{ 
   int minimum; 
   int maximum; 
}; 
  

int getMid(int s, int e) {  return s + (e -s)/2;  } 
  
struct node MaxMinUntill(struct node *st, int ss, int se, int qs, 
                         int qe, int index) 
{ 
    
    struct node tmp,left,right; 
    if (qs <= ss && qe >= se) 
        return st[index]; 
  
    
    if (se < qs || ss > qe) 
    { 
       tmp.minimum = INT_MAX; 
       tmp.maximum = INT_MIN; 
       return tmp; 
     } 
  
    int mid = getMid(ss, se); 
    left = MaxMinUntill(st, ss, mid, qs, qe, 2*index+1); 
    right = MaxMinUntill(st, mid+1, se, qs, qe, 2*index+2); 
    tmp.minimum = min(left.minimum, right.minimum); 
    tmp.maximum = max(left.maximum, right.maximum); 
    return tmp; 
} 
  
 
struct node MaxMin(struct node *st, int n, int qs, int qe) 
{ 
    struct node tmp; 
  
    if (qs < 0 || qe > n-1 || qs > qe) 
    { 
        printf("Invalid Input"); 
        tmp.minimum = INT_MIN; 
        tmp.minimum = INT_MAX; 
        return tmp; 
    } 
  
    return MaxMinUntill(st, 0, n-1, qs, qe, 0); 
} 
  
void constructSTUtil(int arr[], int ss, int se, struct node *st, int si) 
{ 
    if (ss == se) 
    { 
        st[si].minimum = arr[ss]; 
        st[si].maximum = arr[ss]; 
        return ; 
    } 
  
    int mid = getMid(ss, se); 
    constructSTUtil(arr, ss, mid, st, si*2+1); 
    constructSTUtil(arr, mid+1, se, st, si*2+2); 
  
    st[si].minimum = min(st[si*2+1].minimum, st[si*2+2].minimum); 
    st[si].maximum = max(st[si*2+1].maximum, st[si*2+2].maximum); 
} 
  
struct node *constructST(int arr[], int n) 
{ 
    int x = (int)(ceil(log2(n))); 
  
    int max_size = 2*(int)pow(2, x) - 1; 
  
    struct node *st = new struct node[max_size]; 
  
    constructSTUtil(arr, 0, n-1, st, 0); 
  
    return st; 
} 


class UnionFind {                                              // OOP style
private:
	vector<int> p, rank, setSize;
	// p = path toward the root of disjoint set; p[i] = i means it is root
	// rank = upper bound of the actual height of the tree; not reliable as accurate measure
	// setSize = size of each disjoint set

	int numSets;
public:
	UnionFind(int N) {
		setSize.assign(N, 1);
		numSets = N;
		rank.assign(N, 0);
		p.assign(N, 0);
		for (int i = 0; i < N; i++) p[i] = i;	// each belongs to its own set
	}

	int findSet(int i) {
		return (p[i] == i) ? i : (p[i] = findSet(p[i]));		// path compression: cut short of the path if possible
	}

	bool isSameSet(int i, int j) {
		return findSet(i) == findSet(j);
	}

	void unionSet(int i, int j) {
		if (!isSameSet(i, j)) {
			numSets--;
			int x = findSet(i), y = findSet(j);
			// rank is used to keep the tree short
			if (rank[x] > rank[y]) { p[y] = x; setSize[x] += setSize[y]; }
			else {
				p[x] = y; setSize[y] += setSize[x];
				if (rank[x] == rank[y]) rank[y]++;
			}
		}
	}

	int numDisjointSets() {		// # of disjoint sets
		return numSets;
	}

	int sizeOfSet(int i) {		// size of set
		return setSize[findSet(i)];
	}
};



vector< vector<int> > v;
int main()
{ 
	int n,d; cin>>n >>d;
	v.resize(n);
	rep(i,1,n){
		int x;
		rep(j,1,d){
			cin>>x;
			v[i-1].pb(x);
		}
	}
	
	int cont = 0;
	rep(i,1,n){
		
		rep(j,i+1,n){
			int x = 0;
			rep(k,0,d-1)
				x += ( (v[i-1][k] - v[j-1][k]) * (v[i-1][k] - v[j-1][k]) );
			double y = sqrt(1.0*x);
			
			if(floor(y) == ceil(y)){
				
				cont++;
			}
		}
		
	}
	cout<<cont;
	return 0;
} 



Measured memory_bytes: 256